From ecbc7201000505580bf7b49d9577acbf8c1ed8fc Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 13 Jun 2023 17:08:12 +0200 Subject: [PATCH 0001/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index c393768e5a..82720ae81c 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c393768e5a3296a693ae635578ab40d85c3d6c26 +Subproject commit 82720ae81cbef1cff942379daca6fd35c8723ccd From 727deae291c54f0dff8147a4031a36c9ebf1983b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 13 Jun 2023 20:53:56 +0200 Subject: [PATCH 0002/1215] start adding type declarations --- src/bindings | 2 +- src/snarky.d.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 82720ae81c..24269abfd2 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 82720ae81cbef1cff942379daca6fd35c8723ccd +Subproject commit 24269abfd2ff00d12566f25c7c306efc54df3fb3 diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d7f24b000d..1f00a734bc 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -264,8 +264,25 @@ declare const Snarky: { squeeze(sponge: unknown): FieldVar; }; }; + + foreignField: { + assertValidElement(x: ForeignFieldVar, p: ForeignFieldConst): void; + add( + x: ForeignFieldVar, + y: ForeignFieldVar, + p: ForeignFieldConst + ): ForeignFieldVar; + mul( + x: ForeignFieldVar, + y: ForeignFieldVar, + p: ForeignFieldConst + ): ForeignFieldVar; + }; }; +type ForeignFieldVar = unknown; +type ForeignFieldConst = unknown; + type JsonGate = { typ: string; wires: { row: number; col: number }[]; From 536e8e0f48ef7a680da61b32a11a0ff8f33b69d5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 13 Jun 2023 21:56:09 +0200 Subject: [PATCH 0003/1215] start writing foreign field constructor --- src/lib/foreign-field.ts | 35 +++++++++++++++++++++++++++++++++++ src/snarky.d.ts | 25 +++++++++++++------------ 2 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 src/lib/foreign-field.ts diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts new file mode 100644 index 0000000000..ca8a03a7b7 --- /dev/null +++ b/src/lib/foreign-field.ts @@ -0,0 +1,35 @@ +import { FieldConst, FieldVar } from './field.js'; + +export { MlForeignFieldVar, MlForeignFieldConst }; + +type MlForeignField = [_: 0, x0: F, x1: F, x2: F]; +type MlForeignFieldVar = MlForeignField; +type MlForeignFieldConst = MlForeignField; + +class ForeignField { + value: MlForeignFieldVar; + + constructor(x: ForeignField | MlForeignFieldVar | bigint | number | string) { + if (x instanceof ForeignField) { + this.value = x.value; + return; + } + // ForeignFieldVar + if (Array.isArray(x)) { + this.value = x; + return; + } + let x0 = BigInt(x); + } +} + +let limbBits = 88n; +let limbMax = (1n << limbBits) - 1n; + +function to3Limbs(x: bigint): [bigint, bigint, bigint] { + let l0 = x & limbMax; + x >>= limbBits; + let l1 = x & limbMax; + let l2 = x >> limbBits; + return [l0, l1, l2]; +} diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 1f00a734bc..e9bf238702 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -11,6 +11,10 @@ import type { MlBytes, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; +import type { + MlForeignFieldVar, + MlForeignFieldConst, +} from './lib/foreign-field.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; @@ -266,23 +270,20 @@ declare const Snarky: { }; foreignField: { - assertValidElement(x: ForeignFieldVar, p: ForeignFieldConst): void; + assertValidElement(x: MlForeignFieldVar, p: MlForeignFieldConst): void; add( - x: ForeignFieldVar, - y: ForeignFieldVar, - p: ForeignFieldConst - ): ForeignFieldVar; + x: MlForeignFieldVar, + y: MlForeignFieldVar, + p: MlForeignFieldConst + ): MlForeignFieldVar; mul( - x: ForeignFieldVar, - y: ForeignFieldVar, - p: ForeignFieldConst - ): ForeignFieldVar; + x: MlForeignFieldVar, + y: MlForeignFieldVar, + p: MlForeignFieldConst + ): MlForeignFieldVar; }; }; -type ForeignFieldVar = unknown; -type ForeignFieldConst = unknown; - type JsonGate = { typ: string; wires: { row: number; col: number }[]; From d509e7a604d7a4d315673c17c2bd2d8f16136ff0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 09:34:39 +0200 Subject: [PATCH 0004/1215] foreign field class with basic IO --- src/bindings | 2 +- src/index.ts | 1 + src/lib/field.ts | 8 +++ src/lib/foreign-field.ts | 102 +++++++++++++++++++++++++---- src/lib/foreign-field.unit-test.ts | 17 +++++ 5 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/lib/foreign-field.unit-test.ts diff --git a/src/bindings b/src/bindings index 24269abfd2..7ab59e35a1 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 24269abfd2ff00d12566f25c7c306efc54df3fb3 +Subproject commit 7ab59e35a1bd499c3c4b1918075721e5c25506bd diff --git a/src/index.ts b/src/index.ts index b49185508f..ea0448ace1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { ProvablePure, Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; +export { createForeignField, ForeignField } from './lib/foreign-field.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export { diff --git a/src/lib/field.ts b/src/lib/field.ts index 2ba4e84534..39196353f7 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -70,6 +70,14 @@ const FieldVar = { isConstant(x: FieldVar): x is ConstantFieldVar { return x[0] === FieldType.Constant; }, + toConstant(x: FieldVar): FieldConst { + if (FieldVar.isConstant(x)) return x[1]; + // TODO: fix OCaml error message, `Can't evaluate prover code outside an as_prover block` + return Snarky.field.readVar(x); + }, + toBigint(x: FieldVar) { + return FieldConst.toBigint(FieldVar.toConstant(x)); + }, // TODO: handle (special) constants add(x: FieldVar, y: FieldVar): FieldVar { return [FieldType.Add, x, y]; diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index ca8a03a7b7..b487e1a1e3 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,31 +1,95 @@ +import { Snarky } from '../snarky.js'; +import { mod } from '../bindings/crypto/finite_field.js'; +import { Tuple } from '../bindings/lib/binable.js'; import { FieldConst, FieldVar } from './field.js'; +// external API +export { createForeignField, ForeignField }; + +// internal API export { MlForeignFieldVar, MlForeignFieldConst }; +const limbBits = 88n; + type MlForeignField = [_: 0, x0: F, x1: F, x2: F]; type MlForeignFieldVar = MlForeignField; type MlForeignFieldConst = MlForeignField; +type ForeignField = InstanceType>; + +function createForeignField(modulus: bigint, { unsafe = false } = {}) { + const p = modulus; + const pMl = MlForeignFieldConst.fromBigint(p); + + // TODO check that p has valid size + if (p <= 0) { + throw Error(`ForeignField: expected modulus to be positive, got ${p}`); + } -class ForeignField { - value: MlForeignFieldVar; + return class ForeignField { + static modulus = p; + value: MlForeignFieldVar; - constructor(x: ForeignField | MlForeignFieldVar | bigint | number | string) { - if (x instanceof ForeignField) { - this.value = x.value; - return; + constructor( + x: ForeignField | MlForeignFieldVar | bigint | number | string + ) { + if (x instanceof ForeignField) { + this.value = x.value; + return; + } + // ForeignFieldVar + if (Array.isArray(x)) { + this.value = x; + return; + } + // constant + let x0 = mod(BigInt(x), p); + this.value = MlForeignFieldVar.fromBigint(x0); } - // ForeignFieldVar - if (Array.isArray(x)) { - this.value = x; - return; + + isConstant() { + let [, ...limbs] = this.value; + return limbs.every(FieldVar.isConstant); } - let x0 = BigInt(x); - } + + toBigInt() { + return MlForeignFieldVar.toBigint(this.value); + } + + // Provable + + static check(x: ForeignField) { + // if the `unsafe` flag is set, we don't add any constraints when creating a new variable + // this means a user has to take care of proper constraining themselves + if (x.isConstant() || unsafe) return; + Snarky.foreignField.assertValidElement(x.value, pMl); + } + }; } -let limbBits = 88n; +// helpers + let limbMax = (1n << limbBits) - 1n; +const MlForeignFieldConst = { + fromBigint(x: bigint): MlForeignFieldConst { + let limbs = mapTuple(to3Limbs(x), FieldConst.fromBigint); + return [0, ...limbs]; + }, + toBigint([, ...limbs]: MlForeignFieldConst): bigint { + return from3Limbs(mapTuple(limbs, FieldConst.toBigint)); + }, +}; + +const MlForeignFieldVar = { + fromBigint(x: bigint): MlForeignFieldVar { + let limbs = mapTuple(to3Limbs(x), FieldVar.constant); + return [0, ...limbs]; + }, + toBigint([, ...limbs]: MlForeignFieldVar): bigint { + return from3Limbs(mapTuple(limbs, FieldVar.toBigint)); + }, +}; + function to3Limbs(x: bigint): [bigint, bigint, bigint] { let l0 = x & limbMax; x >>= limbBits; @@ -33,3 +97,15 @@ function to3Limbs(x: bigint): [bigint, bigint, bigint] { let l2 = x >> limbBits; return [l0, l1, l2]; } + +function from3Limbs(limbs: [bigint, bigint, bigint]): bigint { + let [l0, l1, l2] = limbs; + return l0 + ((l1 + (l2 << limbBits)) << limbBits); +} + +function mapTuple>( + tuple: T, + f: (a: A) => B +): { [i in keyof T]: B } { + return tuple.map(f) as any; +} diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts new file mode 100644 index 0000000000..51055e5e76 --- /dev/null +++ b/src/lib/foreign-field.unit-test.ts @@ -0,0 +1,17 @@ +import { ForeignField, createForeignField } from './foreign-field.js'; +import { Provable } from './provable.js'; +import { Scalar } from './scalar.js'; +import { expect } from 'expect'; + +let ForeignScalar = createForeignField(Scalar.ORDER); + +// types +// ForeignScalar satisfies Provable; + +// basic constructor / IO +let scalar = new ForeignScalar(1n << 88n); + +console.dir(scalar, { depth: Infinity }); +console.dir(ForeignScalar.modulus, { depth: Infinity }); + +expect(scalar.toBigInt()).toEqual(1n << 88n); From aa8c1747b2ab715a5ec1b73d6d80d80ef2fb25c2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 10:16:53 +0200 Subject: [PATCH 0005/1215] provable foreign field --- src/lib/foreign-field.ts | 47 ++++++++++++++++++++++++++++-- src/lib/foreign-field.unit-test.ts | 4 +-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index b487e1a1e3..c3830cabe5 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { mod } from '../bindings/crypto/finite_field.js'; import { Tuple } from '../bindings/lib/binable.js'; -import { FieldConst, FieldVar } from './field.js'; +import { Field, FieldConst, FieldVar } from './field.js'; // external API export { createForeignField, ForeignField }; @@ -57,6 +57,23 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // Provable + static toFields(x: ForeignField) { + let [, ...limbs] = x.value; + return limbs.map((x) => new Field(x)); + } + static toAuxiliary(): [] { + return []; + } + static sizeInFields() { + return 3; + } + + static fromFields(fields: Field[]) { + let fieldVars = fields.map((x) => x.value); + let limbs = arrayToTuple(fieldVars, 3, 'ForeignField.fromFields()'); + return new ForeignField([0, ...limbs]); + } + static check(x: ForeignField) { // if the `unsafe` flag is set, we don't add any constraints when creating a new variable // this means a user has to take care of proper constraining themselves @@ -103,9 +120,33 @@ function from3Limbs(limbs: [bigint, bigint, bigint]): bigint { return l0 + ((l1 + (l2 << limbBits)) << limbBits); } -function mapTuple>( +function mapTuple, B>( tuple: T, - f: (a: A) => B + f: (a: T[number]) => B ): { [i in keyof T]: B } { return tuple.map(f) as any; } + +// awesome tuple type that has the length as generic parameter + +type TupleN = N extends N + ? number extends N + ? T[] + : _TupleOf + : never; +type _TupleOf = R['length'] extends N + ? R + : _TupleOf; + +function arrayToTuple( + arr: E[], + size: N, + name: string +): TupleN { + if (arr.length !== size) { + throw Error( + `${name}: expected array of length ${size}, got length ${arr.length}` + ); + } + return arr as any; +} diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 51055e5e76..39d3d61972 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,12 +1,12 @@ +import { ProvablePure } from '../snarky.js'; import { ForeignField, createForeignField } from './foreign-field.js'; -import { Provable } from './provable.js'; import { Scalar } from './scalar.js'; import { expect } from 'expect'; let ForeignScalar = createForeignField(Scalar.ORDER); // types -// ForeignScalar satisfies Provable; +ForeignScalar satisfies ProvablePure; // basic constructor / IO let scalar = new ForeignScalar(1n << 88n); From b15a9e007aa37584aaca96f645be499e31e0bd13 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 10:54:28 +0200 Subject: [PATCH 0006/1215] first ff operation: add --- src/lib/foreign-field.ts | 42 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index c3830cabe5..d0ad9b380e 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -25,7 +25,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { throw Error(`ForeignField: expected modulus to be positive, got ${p}`); } - return class ForeignField { + class ForeignField { static modulus = p; value: MlForeignFieldVar; @@ -42,8 +42,14 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return; } // constant - let x0 = mod(BigInt(x), p); - this.value = MlForeignFieldVar.fromBigint(x0); + this.value = MlForeignFieldVar.fromBigint(mod(BigInt(x), p)); + } + + static from( + x: ForeignField | MlForeignFieldVar | bigint | number | string + ) { + if (x instanceof ForeignField) return x; + return new ForeignField(x); } isConstant() { @@ -55,6 +61,17 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return MlForeignFieldVar.toBigint(this.value); } + // arithmetic with full constraints, for safe use + + add(y: ForeignField | bigint | number) { + if (this.isConstant() && isConstant(y)) { + let z = mod(this.toBigInt() + toFp(y), p); + return new ForeignField(z); + } + let z = Snarky.foreignField.add(this.value, toVar(y), pMl); + return new ForeignField(z); + } + // Provable static toFields(x: ForeignField) { @@ -80,7 +97,24 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { if (x.isConstant() || unsafe) return; Snarky.foreignField.assertValidElement(x.value, pMl); } - }; + } + + function toFp(x: bigint | string | number | ForeignField) { + if (x instanceof ForeignField) return x.toBigInt(); + return mod(BigInt(x), p); + } + function toVar( + x: bigint | number | string | ForeignField + ): MlForeignFieldVar { + if (x instanceof ForeignField) return x.value; + return MlForeignFieldVar.fromBigint(mod(BigInt(x), p)); + } + function isConstant(x: bigint | number | string | ForeignField) { + if (x instanceof ForeignField) return x.isConstant(); + return true; + } + + return ForeignField; } // helpers From 9efb8b7a3f3889a246bfeed89e12ef5d36d448c5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 11:42:13 +0200 Subject: [PATCH 0007/1215] finish basic arithmetic, change type name --- src/bindings | 2 +- src/lib/foreign-field.ts | 128 +++++++++++++++++++++++++++++++-------- src/snarky.d.ts | 27 +++++---- 3 files changed, 119 insertions(+), 38 deletions(-) diff --git a/src/bindings b/src/bindings index 7ab59e35a1..263e6f3b02 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7ab59e35a1bd499c3c4b1918075721e5c25506bd +Subproject commit 263e6f3b02db071129d869c5d1726a789ba237cc diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index d0ad9b380e..f9996e3f97 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,37 +1,37 @@ import { Snarky } from '../snarky.js'; -import { mod } from '../bindings/crypto/finite_field.js'; +import { mod, inverse } from '../bindings/crypto/finite_field.js'; import { Tuple } from '../bindings/lib/binable.js'; import { Field, FieldConst, FieldVar } from './field.js'; +import { Provable } from './provable.js'; // external API export { createForeignField, ForeignField }; // internal API -export { MlForeignFieldVar, MlForeignFieldConst }; +export { ForeignFieldVar, ForeignFieldConst }; const limbBits = 88n; type MlForeignField = [_: 0, x0: F, x1: F, x2: F]; -type MlForeignFieldVar = MlForeignField; -type MlForeignFieldConst = MlForeignField; +type ForeignFieldVar = MlForeignField; +type ForeignFieldConst = MlForeignField; type ForeignField = InstanceType>; function createForeignField(modulus: bigint, { unsafe = false } = {}) { const p = modulus; - const pMl = MlForeignFieldConst.fromBigint(p); + const pMl = ForeignFieldConst.fromBigint(p); // TODO check that p has valid size + // also, maybe check that p is a prime? or does that unnecessarily limit use cases? if (p <= 0) { throw Error(`ForeignField: expected modulus to be positive, got ${p}`); } class ForeignField { static modulus = p; - value: MlForeignFieldVar; + value: ForeignFieldVar; - constructor( - x: ForeignField | MlForeignFieldVar | bigint | number | string - ) { + constructor(x: ForeignField | ForeignFieldVar | bigint | number | string) { if (x instanceof ForeignField) { this.value = x.value; return; @@ -42,12 +42,10 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return; } // constant - this.value = MlForeignFieldVar.fromBigint(mod(BigInt(x), p)); + this.value = ForeignFieldVar.fromBigint(mod(BigInt(x), p)); } - static from( - x: ForeignField | MlForeignFieldVar | bigint | number | string - ) { + static from(x: ForeignField | ForeignFieldVar | bigint | number | string) { if (x instanceof ForeignField) return x; return new ForeignField(x); } @@ -57,8 +55,21 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return limbs.every(FieldVar.isConstant); } + toConstant(): ForeignField { + let [, ...limbs] = this.value; + let constantLimbs = mapTuple(limbs, (l) => + FieldVar.constant(FieldVar.toConstant(l)) + ); + return new ForeignField([0, ...constantLimbs]); + } + toBigInt() { - return MlForeignFieldVar.toBigint(this.value); + return ForeignFieldVar.toBigint(this.value); + } + + assertValidElement() { + if (this.isConstant()) return; + Snarky.foreignField.assertValidElement(this.value, pMl); } // arithmetic with full constraints, for safe use @@ -72,12 +83,66 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField(z); } + neg() { + if (this.isConstant()) { + let x = this.toBigInt(); + let z = x === 0n ? 0n : p - x; + return new ForeignField(z); + } + let z = Snarky.foreignField.sub(ForeignFieldVar[0], this.value, pMl); + return new ForeignField(z); + } + + sub(y: ForeignField | bigint | number) { + if (this.isConstant() && isConstant(y)) { + let z = mod(this.toBigInt() - toFp(y), p); + return new ForeignField(z); + } + let z = Snarky.foreignField.sub(this.value, toVar(y), pMl); + return new ForeignField(z); + } + + mul(y: ForeignField | bigint | number) { + if (this.isConstant() && isConstant(y)) { + let z = mod(this.toBigInt() * toFp(y), p); + return new ForeignField(z); + } + let z = Snarky.foreignField.mul(this.value, toVar(y), pMl); + return new ForeignField(z); + } + + inv(): ForeignField { + if (this.isConstant()) { + let z = inverse(this.toBigInt(), p); + if (z === undefined) { + // TODO: if we allow p to be non-prime, change this error message + throw Error('ForeignField.inv(): division by zero'); + } + return new ForeignField(z); + } + let z = Provable.witness(ForeignField, () => this.toConstant().inv()); + + // in unsafe mode, `witness` didn't constrain z to be a valid field element + if (unsafe) z.assertValidElement(); + + // check that x * z === 1 + // TODO: range checks added by `mul` on `one` are unnecessary, since we already assert that `one` equals 1 + let one = Snarky.foreignField.mul(this.value, z.value, pMl); + Provable.assertEqual(new ForeignField(one), new ForeignField(1)); + + return z; + } + // Provable static toFields(x: ForeignField) { let [, ...limbs] = x.value; return limbs.map((x) => new Field(x)); } + toFields() { + return ForeignField.toFields(this); + } + static toAuxiliary(): [] { return []; } @@ -94,8 +159,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { static check(x: ForeignField) { // if the `unsafe` flag is set, we don't add any constraints when creating a new variable // this means a user has to take care of proper constraining themselves - if (x.isConstant() || unsafe) return; - Snarky.foreignField.assertValidElement(x.value, pMl); + if (!unsafe) x.assertValidElement(); } } @@ -103,11 +167,9 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { if (x instanceof ForeignField) return x.toBigInt(); return mod(BigInt(x), p); } - function toVar( - x: bigint | number | string | ForeignField - ): MlForeignFieldVar { + function toVar(x: bigint | number | string | ForeignField): ForeignFieldVar { if (x instanceof ForeignField) return x.value; - return MlForeignFieldVar.fromBigint(mod(BigInt(x), p)); + return ForeignFieldVar.fromBigint(mod(BigInt(x), p)); } function isConstant(x: bigint | number | string | ForeignField) { if (x instanceof ForeignField) return x.isConstant(); @@ -121,24 +183,38 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { let limbMax = (1n << limbBits) - 1n; -const MlForeignFieldConst = { - fromBigint(x: bigint): MlForeignFieldConst { +const ForeignFieldConst = { + fromBigint(x: bigint): ForeignFieldConst { let limbs = mapTuple(to3Limbs(x), FieldConst.fromBigint); return [0, ...limbs]; }, - toBigint([, ...limbs]: MlForeignFieldConst): bigint { + toBigint([, ...limbs]: ForeignFieldConst): bigint { return from3Limbs(mapTuple(limbs, FieldConst.toBigint)); }, + [0]: [ + 0, + FieldConst[0], + FieldConst[0], + FieldConst[0], + ] satisfies ForeignFieldConst, + [1]: [ + 0, + FieldConst[1], + FieldConst[0], + FieldConst[0], + ] satisfies ForeignFieldConst, }; -const MlForeignFieldVar = { - fromBigint(x: bigint): MlForeignFieldVar { +const ForeignFieldVar = { + fromBigint(x: bigint): ForeignFieldVar { let limbs = mapTuple(to3Limbs(x), FieldVar.constant); return [0, ...limbs]; }, - toBigint([, ...limbs]: MlForeignFieldVar): bigint { + toBigint([, ...limbs]: ForeignFieldVar): bigint { return from3Limbs(mapTuple(limbs, FieldVar.toBigint)); }, + [0]: [0, FieldVar[0], FieldVar[0], FieldVar[0]] satisfies ForeignFieldVar, + [1]: [0, FieldVar[1], FieldVar[0], FieldVar[0]] satisfies ForeignFieldVar, }; function to3Limbs(x: bigint): [bigint, bigint, bigint] { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index e9bf238702..9bad8ee2be 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -12,8 +12,8 @@ import type { } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; import type { - MlForeignFieldVar, - MlForeignFieldConst, + ForeignFieldVar, + ForeignFieldConst, } from './lib/foreign-field.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; @@ -270,17 +270,22 @@ declare const Snarky: { }; foreignField: { - assertValidElement(x: MlForeignFieldVar, p: MlForeignFieldConst): void; + assertValidElement(x: ForeignFieldVar, p: ForeignFieldConst): void; add( - x: MlForeignFieldVar, - y: MlForeignFieldVar, - p: MlForeignFieldConst - ): MlForeignFieldVar; + x: ForeignFieldVar, + y: ForeignFieldVar, + p: ForeignFieldConst + ): ForeignFieldVar; + sub( + x: ForeignFieldVar, + y: ForeignFieldVar, + p: ForeignFieldConst + ): ForeignFieldVar; mul( - x: MlForeignFieldVar, - y: MlForeignFieldVar, - p: MlForeignFieldConst - ): MlForeignFieldVar; + x: ForeignFieldVar, + y: ForeignFieldVar, + p: ForeignFieldConst + ): ForeignFieldVar; }; }; From 736cab3f29a47f4d4b6ddcfdb6e86b5bbb53896a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 12:25:59 +0200 Subject: [PATCH 0008/1215] make equivalence testing helpers more generic --- src/lib/field.unit-test.ts | 156 +------------------------------- src/lib/testing/equivalent.ts | 165 ++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 152 deletions(-) create mode 100644 src/lib/testing/equivalent.ts diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 657a8c4dbd..39a24ffef3 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -7,6 +7,7 @@ import { Provable } from './provable.js'; import { Binable } from '../bindings/lib/binable.js'; import { ProvableExtended } from './circuit_value.js'; import { FieldType } from './field.js'; +import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; // types Field satisfies Provable; @@ -61,6 +62,9 @@ let SmallField = Random.reject( (x) => x.toString(2).length > Fp.sizeInBits - 2 ); +let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = + createEquivalenceTesters(Field, Field); + // arithmetic, both in- and outside provable code equivalent2((x, y) => x.add(y), Fp.add); equivalent1((x) => x.neg(), Fp.negate); @@ -183,155 +187,3 @@ test(Random.field, Random.field, (x0, y0, assert) => { ); }); }); - -// helpers - -function equivalent1( - op1: (x: Field) => Field, - op2: (x: bigint) => bigint, - rng: Random = Random.field -) { - test(rng, (x0, assert) => { - let x = Field(x0); - // outside provable code - handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - }); - }); -} -function equivalent2( - op1: (x: Field, y: Field | bigint) => Field, - op2: (x: bigint, y: bigint) => bigint, - rng: Random = Random.field -) { - test(rng, rng, (x0, y0, assert) => { - let x = Field(x0); - let y = Field(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - }); - }); -} -function equivalentVoid1( - op1: (x: Field) => void, - op2: (x: bigint) => void, - rng: Random = Random.field -) { - test(rng, (x0) => { - let x = Field(x0); - // outside provable code - handleErrors( - () => op1(x), - () => op2(x0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - handleErrors( - () => op1(x), - () => op2(x0) - ); - }); - }); -} -function equivalentVoid2( - op1: (x: Field, y: Field | bigint) => void, - op2: (x: bigint, y: bigint) => void, - rng: Random = Random.field -) { - test(rng, rng, (x0, y0) => { - let x = Field(x0); - let y = Field(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - }); - }); -} - -function handleErrors( - op1: () => T, - op2: () => S, - useResults?: (a: T, b: S) => R -): R | undefined { - let result1: T, result2: S; - let error1: Error | undefined; - let error2: Error | undefined; - try { - result1 = op1(); - } catch (err) { - error1 = err as Error; - } - try { - result2 = op2(); - } catch (err) { - error2 = err as Error; - } - if (!!error1 !== !!error2) { - error1 && console.log(error1); - error2 && console.log(error2); - } - deepEqual(!!error1, !!error2, 'equivalent errors'); - if (!(error1 || error2) && useResults !== undefined) { - return useResults(result1!, result2!); - } -} - -function throwError(message?: string): any { - throw Error(message); -} diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts new file mode 100644 index 0000000000..24ae3e846a --- /dev/null +++ b/src/lib/testing/equivalent.ts @@ -0,0 +1,165 @@ +/** + * helpers for testing equivalence of two implementations, one of them on bigints + */ +import { test, Random } from '../testing/property.js'; +import { Provable } from '../provable.js'; +import { deepEqual } from 'node:assert/strict'; + +export { createEquivalenceTesters, throwError }; + +function createEquivalenceTesters( + Field: Provable, + newField: (x: bigint) => Field +) { + function equivalent1( + op1: (x: Field) => Field, + op2: (x: bigint) => bigint, + rng: Random = Random.field + ) { + test(rng, (x0, assert) => { + let x = newField(x0); + // outside provable code + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + }); + }); + } + function equivalent2( + op1: (x: Field, y: Field | bigint) => Field, + op2: (x: bigint, y: bigint) => bigint, + rng: Random = Random.field + ) { + test(rng, rng, (x0, y0, assert) => { + let x = newField(x0); + let y = newField(y0); + // outside provable code + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + y = Provable.witness(Field, () => y); + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + }); + }); + } + function equivalentVoid1( + op1: (x: Field) => void, + op2: (x: bigint) => void, + rng: Random = Random.field + ) { + test(rng, (x0) => { + let x = newField(x0); + // outside provable code + handleErrors( + () => op1(x), + () => op2(x0) + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + handleErrors( + () => op1(x), + () => op2(x0) + ); + }); + }); + } + function equivalentVoid2( + op1: (x: Field, y: Field | bigint) => void, + op2: (x: bigint, y: bigint) => void, + rng: Random = Random.field + ) { + test(rng, rng, (x0, y0) => { + let x = newField(x0); + let y = newField(y0); + // outside provable code + handleErrors( + () => op1(x, y), + () => op2(x0, y0) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0) + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + y = Provable.witness(Field, () => y); + handleErrors( + () => op1(x, y), + () => op2(x0, y0) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0) + ); + }); + }); + } + + function handleErrors( + op1: () => T, + op2: () => S, + useResults?: (a: T, b: S) => R + ): R | undefined { + let result1: T, result2: S; + let error1: Error | undefined; + let error2: Error | undefined; + try { + result1 = op1(); + } catch (err) { + error1 = err as Error; + } + try { + result2 = op2(); + } catch (err) { + error2 = err as Error; + } + if (!!error1 !== !!error2) { + error1 && console.log(error1); + error2 && console.log(error2); + } + deepEqual(!!error1, !!error2, 'equivalent errors'); + if (!(error1 || error2) && useResults !== undefined) { + return useResults(result1!, result2!); + } + } + + return { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 }; +} + +function throwError(message?: string): any { + throw Error(message); +} From a5c65400200fb4560cd504992c3d2759c1385fb7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 12:26:20 +0200 Subject: [PATCH 0009/1215] add equals, assertEquals --- src/lib/foreign-field.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index f9996e3f97..3a92e06fd7 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,14 +1,15 @@ import { Snarky } from '../snarky.js'; import { mod, inverse } from '../bindings/crypto/finite_field.js'; import { Tuple } from '../bindings/lib/binable.js'; -import { Field, FieldConst, FieldVar } from './field.js'; +import { Field, FieldConst, FieldVar, withMessage } from './field.js'; import { Provable } from './provable.js'; +import { Bool } from './bool.js'; // external API export { createForeignField, ForeignField }; // internal API -export { ForeignFieldVar, ForeignFieldConst }; +export { ForeignFieldVar, ForeignFieldConst, limbBits }; const limbBits = 88n; @@ -128,11 +129,35 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // check that x * z === 1 // TODO: range checks added by `mul` on `one` are unnecessary, since we already assert that `one` equals 1 let one = Snarky.foreignField.mul(this.value, z.value, pMl); - Provable.assertEqual(new ForeignField(one), new ForeignField(1)); + new ForeignField(one).assertEquals(new ForeignField(1)); return z; } + // convenience methods + + assertEquals(y: ForeignField | bigint | number, message?: string) { + try { + if (this.isConstant() && isConstant(y)) { + let x = this.toBigInt(); + let y0 = toFp(y); + if (x !== y0) { + throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); + } + } + return Provable.assertEqual(ForeignField, this, ForeignField.from(y)); + } catch (err) { + throw withMessage(err, message); + } + } + + equals(y: ForeignField | bigint | number) { + if (this.isConstant() && isConstant(y)) { + return new Bool(this.toBigInt() === toFp(y)); + } + return Provable.equal(ForeignField, this, ForeignField.from(y)); + } + // Provable static toFields(x: ForeignField) { From 4212c9d6f8fe04407518a5f5010bcd0a6e37d050 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 12:26:30 +0200 Subject: [PATCH 0010/1215] add arithemtic tests --- src/lib/foreign-field.unit-test.ts | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 39d3d61972..344a887819 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,7 +1,11 @@ import { ProvablePure } from '../snarky.js'; -import { ForeignField, createForeignField } from './foreign-field.js'; +import { FieldVar } from './field.js'; +import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; +import { Scalar as Fq } from '../provable/curve-bigint.js'; import { Scalar } from './scalar.js'; import { expect } from 'expect'; +import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; +import { Random } from './testing/random.js'; let ForeignScalar = createForeignField(Scalar.ORDER); @@ -9,9 +13,30 @@ let ForeignScalar = createForeignField(Scalar.ORDER); ForeignScalar satisfies ProvablePure; // basic constructor / IO -let scalar = new ForeignScalar(1n << 88n); +let s0 = 1n + ((1n + (1n << limbBits)) << limbBits); +let scalar = new ForeignScalar(s0); -console.dir(scalar, { depth: Infinity }); -console.dir(ForeignScalar.modulus, { depth: Infinity }); +expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); +expect(scalar.toBigInt()).toEqual(s0); -expect(scalar.toBigInt()).toEqual(1n << 88n); +let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = + createEquivalenceTesters(ForeignScalar, (x) => new ForeignScalar(x)); + +equivalent2((x, y) => x.add(y), Fq.add, Random.scalar); +equivalent1((x) => x.neg(), Fq.negate, Random.scalar); +equivalent2((x, y) => x.sub(y), Fq.sub, Random.scalar); +equivalent2((x, y) => x.mul(y), Fq.mul, Random.scalar); +equivalent1( + (x) => x.inv(), + (x) => Fq.inverse(x) ?? throwError('division by 0'), + Random.scalar +); + +// equivalent2( +// (x, y) => x.equals(y).toField(), +// (x, y) => BigInt(x === y) +// ); +// equivalentVoid2( +// (x, y) => x.assertEquals(y), +// (x, y) => x === y || throwError('not equal') +// ); From 43d854c11fd4f1c9cc64c48ce38984698f410893 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:09:27 +0200 Subject: [PATCH 0011/1215] add equialence test with boolean output --- src/lib/field.unit-test.ts | 27 +++++++----- src/lib/testing/equivalent.ts | 79 ++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 39a24ffef3..3cee9b0ab0 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -62,8 +62,13 @@ let SmallField = Random.reject( (x) => x.toString(2).length > Fp.sizeInBits - 2 ); -let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = - createEquivalenceTesters(Field, Field); +let { + equivalent1, + equivalent2, + equivalentBool2, + equivalentVoid1, + equivalentVoid2, +} = createEquivalenceTesters(Field, Field); // arithmetic, both in- and outside provable code equivalent2((x, y) => x.add(y), Fp.add); @@ -83,18 +88,18 @@ equivalent1( (x) => x.sqrt(), (x) => Fp.sqrt(x) ?? throwError('no sqrt') ); -equivalent2( - (x, y) => x.equals(y).toField(), - (x, y) => BigInt(x === y) +equivalentBool2( + (x, y) => x.equals(y), + (x, y) => x === y ); -equivalent2( - (x, y) => x.lessThan(y).toField(), - (x, y) => BigInt(x < y), +equivalentBool2( + (x, y) => x.lessThan(y), + (x, y) => x < y, SmallField ); -equivalent2( - (x, y) => x.lessThanOrEqual(y).toField(), - (x, y) => BigInt(x <= y), +equivalentBool2( + (x, y) => x.lessThanOrEqual(y), + (x, y) => x <= y, SmallField ); equivalentVoid2( diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 24ae3e846a..a8089d251a 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -4,6 +4,7 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; +import { Bool } from '../bool.js'; export { createEquivalenceTesters, throwError }; @@ -74,6 +75,75 @@ function createEquivalenceTesters( }); }); } + function equivalentBool1( + op1: (x: Field) => Bool, + op2: (x: bigint) => boolean, + rng: Random = Random.field + ) { + test(rng, (x0, assert) => { + let x = newField(x0); + // outside provable code + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => assert(a.toBoolean() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => + Provable.asProver(() => + assert(a.toBoolean() === b, 'equal results') + ) + ); + }); + }); + } + function equivalentBool2( + op1: (x: Field, y: Field | bigint) => Bool, + op2: (x: bigint, y: bigint) => boolean, + rng: Random = Random.field + ) { + test(rng, rng, (x0, y0, assert) => { + let x = newField(x0); + let y = newField(y0); + // outside provable code + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => assert(a.toBoolean() === b, 'equal results') + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => assert(a.toBoolean() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + y = Provable.witness(Field, () => y); + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => + assert(a.toBoolean() === b, 'equal results') + ) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => + assert(a.toBoolean() === b, 'equal results') + ) + ); + }); + }); + } function equivalentVoid1( op1: (x: Field) => void, op2: (x: bigint) => void, @@ -157,7 +227,14 @@ function createEquivalenceTesters( } } - return { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 }; + return { + equivalent1, + equivalent2, + equivalentBool1, + equivalentBool2, + equivalentVoid1, + equivalentVoid2, + }; } function throwError(message?: string): any { From c4e239b0c443270fa4778329901fcb548987da8c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:09:38 +0200 Subject: [PATCH 0012/1215] factor out helper --- src/lib/field.ts | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 39196353f7..d699875818 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -9,7 +9,15 @@ import { Bool } from './bool.js'; export { Field }; // internal API -export { ConstantField, FieldType, FieldVar, FieldConst, isField, withMessage }; +export { + ConstantField, + FieldType, + FieldVar, + FieldConst, + isField, + withMessage, + checkBitLength, +}; type FieldConst = Uint8Array; @@ -886,15 +894,6 @@ class Field { } } - static #checkBitLength(name: string, length: number) { - if (length > Fp.sizeInBits) - throw Error( - `${name}: bit length must be ${Fp.sizeInBits} or less, got ${length}` - ); - if (length <= 0) - throw Error(`${name}: bit length must be positive, got ${length}`); - } - /** * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. * @@ -909,7 +908,7 @@ class Field { * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ toBits(length?: number) { - if (length !== undefined) Field.#checkBitLength('Field.toBits()', length); + if (length !== undefined) checkBitLength('Field.toBits()', length); if (this.isConstant()) { let bits = Fp.toBits(this.toBigInt()); if (length !== undefined) { @@ -936,7 +935,7 @@ class Field { */ static fromBits(bits: (Bool | boolean)[]) { let length = bits.length; - Field.#checkBitLength('Field.fromBits()', length); + checkBitLength('Field.fromBits()', length); if (bits.every((b) => typeof b === 'boolean' || b.toField().isConstant())) { let bits_ = bits .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) @@ -964,7 +963,7 @@ class Field { * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. */ rangeCheckHelper(length: number) { - Field.#checkBitLength('Field.rangeCheckHelper()', length); + checkBitLength('Field.rangeCheckHelper()', length); if (length % 16 !== 0) throw Error( 'Field.rangeCheckHelper(): `length` has to be a multiple of 16.' @@ -1219,6 +1218,8 @@ class Field { static sizeInBytes() { return Fp.sizeInBytes(); } + + static sizeInBits = Fp.sizeInBits; } const FieldBinable = defineBinable({ @@ -1264,3 +1265,16 @@ function withMessage(error: unknown, message?: string) { error.message = `${message}\n${error.message}`; return error; } + +function checkBitLength( + name: string, + length: number, + maxLength = Fp.sizeInBits +) { + if (length > maxLength) + throw Error( + `${name}: bit length must be ${maxLength} or less, got ${length}` + ); + if (length < 0) + throw Error(`${name}: bit length must be non-negative, got ${length}`); +} From abdd1bdf3f39cbe0a1b9d1cb5e7d8b203b7ec85e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:10:01 +0200 Subject: [PATCH 0013/1215] implement to/from bits --- src/lib/foreign-field.ts | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 3a92e06fd7..39d7e76ab2 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,7 +1,13 @@ import { Snarky } from '../snarky.js'; import { mod, inverse } from '../bindings/crypto/finite_field.js'; import { Tuple } from '../bindings/lib/binable.js'; -import { Field, FieldConst, FieldVar, withMessage } from './field.js'; +import { + Field, + FieldConst, + FieldVar, + checkBitLength, + withMessage, +} from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; @@ -28,6 +34,8 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { throw Error(`ForeignField: expected modulus to be positive, got ${p}`); } + let sizeInBits = p.toString(2).length; + class ForeignField { static modulus = p; value: ForeignFieldVar; @@ -158,6 +166,35 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return Provable.equal(ForeignField, this, ForeignField.from(y)); } + // bit packing + + toBits(length = sizeInBits) { + checkBitLength('ForeignField.toBits()', length, sizeInBits); + let [l0, l1, l2] = this.toFields(); + let limbSize = Number(limbBits); + let xBits = l0.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return xBits; + let yBits = l1.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return [...xBits, ...yBits]; + let zBits = l2.toBits(Math.min(length, limbSize)); + return [...xBits, ...yBits, ...zBits]; + } + + static fromBits(bits: Bool[]) { + let length = bits.length; + checkBitLength('ForeignField.fromBits()', length, sizeInBits); + let limbSize = Number(limbBits); + 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)); + let x = ForeignField.fromFields([l0, l1, l2]); + // TODO: this is inefficient, l0, l1 are already range-checked + if (length === sizeInBits) x.assertValidElement(); + return x; + } + // Provable static toFields(x: ForeignField) { From 5a63a2e27281936e6d7c542a500b8bca04efcc4c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:10:07 +0200 Subject: [PATCH 0014/1215] add more tests --- src/lib/foreign-field.unit-test.ts | 36 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 344a887819..cdaa5861aa 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -13,15 +13,19 @@ let ForeignScalar = createForeignField(Scalar.ORDER); ForeignScalar satisfies ProvablePure; // basic constructor / IO + let s0 = 1n + ((1n + (1n << limbBits)) << limbBits); let scalar = new ForeignScalar(s0); expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); expect(scalar.toBigInt()).toEqual(s0); -let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = +// test equivalence of in-SNARK and out-of-SNARK operations + +let { equivalent1, equivalent2, equivalentBool2, equivalentVoid2 } = createEquivalenceTesters(ForeignScalar, (x) => new ForeignScalar(x)); +// arithmetic equivalent2((x, y) => x.add(y), Fq.add, Random.scalar); equivalent1((x) => x.neg(), Fq.negate, Random.scalar); equivalent2((x, y) => x.sub(y), Fq.sub, Random.scalar); @@ -32,11 +36,25 @@ equivalent1( Random.scalar ); -// equivalent2( -// (x, y) => x.equals(y).toField(), -// (x, y) => BigInt(x === y) -// ); -// equivalentVoid2( -// (x, y) => x.assertEquals(y), -// (x, y) => x === y || throwError('not equal') -// ); +// equality +equivalentBool2( + (x, y) => x.equals(y), + (x, y) => x === y, + Random.scalar +); +equivalentVoid2( + (x, y) => x.assertEquals(y), + (x, y) => x === y || throwError('not equal'), + Random.scalar +); + +// toBits / fromBits +equivalent1( + (x) => { + let bits = x.toBits(); + expect(bits.length).toEqual(255); + return ForeignScalar.fromBits(bits); + }, + (x) => x, + Random.scalar +); From 42a3c114b00e9c89d25ebfab60ce6ccbcdc86767 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:14:14 +0200 Subject: [PATCH 0015/1215] tweak --- src/lib/foreign-field.unit-test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index cdaa5861aa..53d716bf32 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -2,12 +2,11 @@ import { ProvablePure } from '../snarky.js'; import { FieldVar } from './field.js'; import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { Scalar } from './scalar.js'; import { expect } from 'expect'; import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; import { Random } from './testing/random.js'; -let ForeignScalar = createForeignField(Scalar.ORDER); +let ForeignScalar = createForeignField(Fq.modulus); // types ForeignScalar satisfies ProvablePure; From 00ef6d30114e327343be4ec8fe987f25dfc108e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 13:20:37 +0200 Subject: [PATCH 0016/1215] another small test --- src/lib/foreign-field.unit-test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 53d716bf32..74eb854931 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -4,7 +4,7 @@ import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; -import { Random } from './testing/random.js'; +import { test, Random } from './testing/property.js'; let ForeignScalar = createForeignField(Fq.modulus); @@ -19,6 +19,12 @@ let scalar = new ForeignScalar(s0); expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); expect(scalar.toBigInt()).toEqual(s0); +test(Random.scalar, (x0, assert) => { + let x = new ForeignScalar(x0); + assert(x.toBigInt() === x0); + assert(x.isConstant()); +}); + // test equivalence of in-SNARK and out-of-SNARK operations let { equivalent1, equivalent2, equivalentBool2, equivalentVoid2 } = From b11a88336384c4d4a32883eb7ea8056cdfb65b33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 15:13:09 +0200 Subject: [PATCH 0017/1215] start adding more complex test --- src/lib/foreign-field.unit-test.ts | 58 +++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 74eb854931..f93a86048e 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -5,6 +5,8 @@ import { Scalar as Fq } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; import { test, Random } from './testing/property.js'; +import { Provable } from './provable.js'; +import { ZkProgram } from './proof_system.js'; let ForeignScalar = createForeignField(Fq.modulus); @@ -12,12 +14,13 @@ let ForeignScalar = createForeignField(Fq.modulus); ForeignScalar satisfies ProvablePure; // basic constructor / IO +{ + let s0 = 1n + ((1n + (1n << limbBits)) << limbBits); + let scalar = new ForeignScalar(s0); -let s0 = 1n + ((1n + (1n << limbBits)) << limbBits); -let scalar = new ForeignScalar(s0); - -expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); -expect(scalar.toBigInt()).toEqual(s0); + expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); + expect(scalar.toBigInt()).toEqual(s0); +} test(Random.scalar, (x0, assert) => { let x = new ForeignScalar(x0); @@ -63,3 +66,48 @@ equivalent1( (x) => x, Random.scalar ); + +// shift + +let scalarShift = Fq(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; + +function shift(s: Fq): Fq { + return Fq.add(Fq.add(s, s), scalarShift); +} + +let scalar = Fq.random(); +let scalarShifted = shift(scalar); + +function main() { + // perform a "scalar shift" in foreign field arithmetic + let x = Provable.witness(ForeignScalar, () => new ForeignScalar(scalar)); + let x2 = x.add(x); + let shifted = x2.add(scalarShift); + shifted.assertEquals(scalarShifted); +} +Provable.runAndCheck(main); +let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(main); + +let Program = ZkProgram({ + methods: { + test: { + privateInputs: [], + method() { + main(); + }, + }, + }, +}); + +// console.log(JSON.stringify(gates)); +console.log({ rows, digest, publicInputSize }); + +console.log('compiling'); +await Program.compile(); + +console.log('proving'); +let proof = await Program.test(); + +let ok = await Program.verify(proof); +console.log('verifies?', ok); From 1e7404541b3c9f2d0fd268367bf88161d819da7a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 14 Jun 2023 16:55:58 +0200 Subject: [PATCH 0018/1215] more testing --- src/bindings | 2 +- src/lib/circuit.ts | 1 + src/lib/foreign-field.unit-test.ts | 40 ++++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/bindings b/src/bindings index 263e6f3b02..4e06a7c20a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 263e6f3b02db071129d869c5d1726a789ba237cc +Subproject commit 4e06a7c20aa64e009b91bb5e7ef2e469c2039cb9 diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index 232f3f4f0d..dd697cc967 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import { ProvablePure, Snarky } from '../snarky.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index f93a86048e..1c625e90c4 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -7,6 +7,7 @@ import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; import { test, Random } from './testing/property.js'; import { Provable } from './provable.js'; import { ZkProgram } from './proof_system.js'; +import { Circuit, circuitMain } from './circuit.js'; let ForeignScalar = createForeignField(Fq.modulus); @@ -79,35 +80,52 @@ function shift(s: Fq): Fq { let scalar = Fq.random(); let scalarShifted = shift(scalar); -function main() { +function main_() { // perform a "scalar shift" in foreign field arithmetic let x = Provable.witness(ForeignScalar, () => new ForeignScalar(scalar)); - let x2 = x.add(x); - let shifted = x2.add(scalarShift); - shifted.assertEquals(scalarShifted); + let shifted = x.add(x).add(scalarShift); + // shifted.assertEquals(scalarShifted); } -Provable.runAndCheck(main); -let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(main); +Provable.runAndCheck(main_); +let { rows, gates } = Provable.constraintSystem(main_); + +console.log({ rows, gates: JSON.stringify(gates) }); + +class Main extends Circuit { + @circuitMain + static main() { + main_(); + } +} + +console.log('compiling'); +let kp = await Main.generateKeypair(); + +let cs = kp.constraintSystem(); +console.log(JSON.stringify(cs.filter((g) => g.type !== 'Zero'))); + +console.log('proving'); +let proof0 = await Main.prove([], [], kp); + +let ok = await Main.verify([], kp.verificationKey(), proof0); +console.log('verifies?', ok); let Program = ZkProgram({ methods: { test: { privateInputs: [], method() { - main(); + main_(); }, }, }, }); -// console.log(JSON.stringify(gates)); -console.log({ rows, digest, publicInputSize }); - console.log('compiling'); await Program.compile(); console.log('proving'); let proof = await Program.test(); -let ok = await Program.verify(proof); +ok = await Program.verify(proof); console.log('verifies?', ok); From d281c3683be3edca6793a3d03e1e6a762e781d00 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 11:06:54 -0700 Subject: [PATCH 0019/1215] feat(snarky.d.ts): add sha.create and sha.fieldBytesFromHex functions to the Snarky type definition file to support SHA hashing and conversion from hex to field bytes. --- src/snarky.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d7f24b000d..bba88fb5ab 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -244,6 +244,16 @@ declare const Snarky: { }; }; + sha: { + create( + message: MlArray, + nist: boolean, + length: number + ): MlArray; + + fieldBytesFromHex(hex: string): MlArray; + }; + poseidon: { hash(input: MlArray, isChecked: boolean): FieldVar; From c85a95911982811e17cc10791ef3501a4914c1ab Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 11:14:37 -0700 Subject: [PATCH 0020/1215] refactor(hash.ts): rename Hash to HashHelpers --- src/lib/hash.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index a2684d655b..dd8dd2262f 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -11,7 +11,7 @@ export { Poseidon, TokenSymbol }; // internal API export { HashInput, - Hash, + HashHelpers, emptyHashWithPrefix, hashWithPrefix, salt, @@ -98,8 +98,8 @@ function hashConstant(input: Field[]) { return Field(digest); } -const Hash = createHashHelpers(Field, Poseidon); -let { salt, emptyHashWithPrefix, hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { salt, emptyHashWithPrefix, hashWithPrefix } = HashHelpers; // same as Random_oracle.prefix_to_field in OCaml function prefixToField(prefix: string) { From 18e9afebce185571f2d33cb1923ef9f5f6354bb7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 11:18:47 -0700 Subject: [PATCH 0021/1215] feat(hash.ts): add support for SHA224, SHA256, SHA385, SHA512, and Keccak256 hash functions --- src/lib/hash.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index dd8dd2262f..f6fe0dee0f 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -6,7 +6,7 @@ import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; // external API -export { Poseidon, TokenSymbol }; +export { Poseidon, TokenSymbol, Hash }; // internal API export { @@ -192,3 +192,29 @@ class TokenSymbol extends Struct(TokenSymbolPure) { function emptyReceiptChainHash() { return emptyHashWithPrefix('CodaReceiptEmpty'); } + +function buildSHA(length: 224 | 256 | 385 | 512, nist: boolean) { + return { + hash(message: Field[]) { + return Snarky.sha + .create([0, ...message.map((f) => f.value)], nist, length) + .map(Field); + }, + }; +} + +const Hash = { + default: Poseidon.hash, + + Poseidon: Poseidon.hash, + + SHA224: buildSHA(224, true).hash, + + SHA256: buildSHA(256, true).hash, + + SHA385: buildSHA(385, true).hash, + + SHA512: buildSHA(512, true).hash, + + Keccack256: buildSHA(256, false).hash, +}; From 556b81fe4a1703537038c581b4049015449525e9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 11:19:36 -0700 Subject: [PATCH 0022/1215] feat(index.ts): export Hash from lib/hash --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index b33f1d5539..e79d0577c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export { ProvablePure, Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; -export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Poseidon, TokenSymbol, Hash } from './lib/hash.js'; export * from './lib/signature.js'; export { CircuitValue, From cb223a5021b61354b9dfda50c2406df20e5c40f6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 11:21:36 -0700 Subject: [PATCH 0023/1215] fix(hash.ts): correct SHA384 function name to match the correct length of 384 bits --- src/lib/hash.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index f6fe0dee0f..003300dca5 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -193,7 +193,7 @@ function emptyReceiptChainHash() { return emptyHashWithPrefix('CodaReceiptEmpty'); } -function buildSHA(length: 224 | 256 | 385 | 512, nist: boolean) { +function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { hash(message: Field[]) { return Snarky.sha @@ -212,7 +212,7 @@ const Hash = { SHA256: buildSHA(256, true).hash, - SHA385: buildSHA(385, true).hash, + SHA384: buildSHA(384, true).hash, SHA512: buildSHA(512, true).hash, From 83432329e4fa30ab5909682c8162dffbb6893f5c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 13:25:29 -0700 Subject: [PATCH 0024/1215] feat(int.ts): add UInt8 class to represent 8-bit unsigned integers in the circuit. --- src/index.ts | 2 +- src/lib/int.ts | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index e79d0577c5..374256f5c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ export { } from './lib/circuit_value.js'; export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; -export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; +export { UInt8, UInt32, UInt64, Int64, Sign } from './lib/int.js'; export { Types } from './bindings/mina-transaction/types.js'; export * as Mina from './lib/mina.js'; diff --git a/src/lib/int.ts b/src/lib/int.ts index 39744a84cf..fca2b6c971 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,11 +1,11 @@ import { Field, Bool } from './core.js'; -import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; +import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; // external API -export { UInt32, UInt64, Int64, Sign }; +export { UInt8, UInt32, UInt64, Int64, Sign }; /** * A 64 bit unsigned integer with values ranging from 0 to 18,446,744,073,709,551,615. @@ -959,3 +959,18 @@ class Int64 extends CircuitValue implements BalanceChange { return this.sgn.isPositive(); } } + +class UInt8 extends Struct({ + value: Field, +}) { + constructor(x: number | Field) { + super({ value: Field(x) }); + + // Make sure that the Field element that is exactly a byte + this.value.toBits(8); + } + + check() { + this.value.toBits(8); + } +} From b6868e0d626b4fd6716fef776a8e6c20ff7d2630 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 13:56:54 -0700 Subject: [PATCH 0025/1215] refactor(hash.ts): add support for hashing UInt8 values --- src/lib/hash.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 003300dca5..3368201e52 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -4,6 +4,8 @@ import { Field } from './core.js'; import { createHashHelpers } from './hash-generic.js'; import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; +import { UInt8 } from './int.js'; +import { isField } from './field.js'; // external API export { Poseidon, TokenSymbol, Hash }; @@ -195,10 +197,21 @@ function emptyReceiptChainHash() { function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { - hash(message: Field[]) { - return Snarky.sha - .create([0, ...message.map((f) => f.value)], nist, length) - .map(Field); + hash(message: (Field | UInt8)[]) { + if (message.length === 0) { + throw Error('SHA hash of empty message'); + } + + const values = message.map((f) => { + if (isField(f)) { + // Make sure that the field is exactly a byte. + f.toBits(8); + return f.value; + } + return f.value.value; + }); + + return Snarky.sha.create([0, ...values], nist, length).map(Field); }, }; } From 3f6da96e74fe73b90efddfef123ef31fb8a1ba3c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 21 Jun 2023 13:58:57 -0700 Subject: [PATCH 0026/1215] feat(keccak.ts): add tests for SHA224, SHA256, SHA384, SHA512, Poseidon, default and Keccack256 --- src/examples/keccak.ts | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/examples/keccak.ts diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts new file mode 100644 index 0000000000..15b582d880 --- /dev/null +++ b/src/examples/keccak.ts @@ -0,0 +1,43 @@ +import { Hash, Field, Provable, UInt8 } from 'snarkyjs'; + +console.log('Running SHA224 test'); +Provable.runAndCheck(() => { + let digest = Hash.SHA224([Field(1), Field(30000), new UInt8(2)]); + Provable.log(digest); +}); + +console.log('Running SHA256 test'); +Provable.runAndCheck(() => { + let digest = Hash.SHA256([Field(1), Field(1), new UInt8(2)]); + Provable.log(digest); +}); + +console.log('Running SHA384 test'); +Provable.runAndCheck(() => { + let digest = Hash.SHA384([Field(1), Field(1), new UInt8(2)]); + Provable.log(digest); +}); + +console.log('Running SHA512 test'); +Provable.runAndCheck(() => { + let digest = Hash.SHA512([Field(1), Field(1), new UInt8(2)]); + Provable.log(digest); +}); + +console.log('Running Poseidon test'); +Provable.runAndCheck(() => { + let digest = Hash.Poseidon([Field(1), Field(1), Field(2)]); + Provable.log(digest); +}); + +console.log('Running default hash test'); +Provable.runAndCheck(() => { + let digest = Hash.default([Field(1), Field(1), Field(2)]); + Provable.log(digest); +}); + +console.log('Running keccack hash test'); +Provable.runAndCheck(() => { + let digest = Hash.Keccack256([Field(1), Field(1), new UInt8(2)]); + Provable.log(digest); +}); From 3cad95efad6022338488f443e90c4d106896562c Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 22 Jun 2023 17:13:38 +0200 Subject: [PATCH 0027/1215] add non-trivial example of ff usage --- src/lib/foreign-field.unit-test.ts | 68 ++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 1c625e90c4..fc9db00f5d 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,13 +1,15 @@ import { ProvablePure } from '../snarky.js'; +import { Group } from './core.js'; import { FieldVar } from './field.js'; import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; -import { Scalar as Fq } from '../provable/curve-bigint.js'; +import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; import { test, Random } from './testing/property.js'; import { Provable } from './provable.js'; import { ZkProgram } from './proof_system.js'; import { Circuit, circuitMain } from './circuit.js'; +import { Scalar } from './scalar.js'; let ForeignScalar = createForeignField(Fq.modulus); @@ -68,29 +70,66 @@ equivalent1( Random.scalar ); -// shift +// scalar shift in foreign field arithmetic vs in the exponent let scalarShift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; -function shift(s: Fq): Fq { - return Fq.add(Fq.add(s, s), scalarShift); +function unshift(s: ForeignField) { + return s.sub(scalarShift).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 scalar = Fq.random(); -let scalarShifted = shift(scalar); +let scalarBigint = Fq.random(); +let pointBigint = G.scale(G.generatorMina, 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, + () => 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)); +} -function main_() { - // perform a "scalar shift" in foreign field arithmetic - let x = Provable.witness(ForeignScalar, () => new ForeignScalar(scalar)); - let shifted = x.add(x).add(scalarShift); - // shifted.assertEquals(scalarShifted); +// go directly from foreign scalar to scalar and perform a shifted scale +// = same end result as main0 +function main1() { + let ffScalar = Provable.witness( + ForeignScalar, + () => 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)); } -Provable.runAndCheck(main_); -let { rows, gates } = Provable.constraintSystem(main_); -console.log({ rows, gates: JSON.stringify(gates) }); +// check provable and non-provable versions are correct +main0(); +main1(); +Provable.runAndCheck(main0); +Provable.runAndCheck(main1); + +// using foreign field arithmetic should result in much fewer constraints +let { rows: rows0 } = Provable.constraintSystem(main0); +let { rows: rows1 } = Provable.constraintSystem(main1); +expect(rows0 + 100).toBeLessThan(rows1); +// TODO: add when proving works +/* class Main extends Circuit { @circuitMain static main() { @@ -129,3 +168,4 @@ let proof = await Program.test(); ok = await Program.verify(proof); console.log('verifies?', ok); + */ From eed823f90d9f1d815b4abf52d7f42b61a0cc80cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 22 Jun 2023 18:30:26 +0200 Subject: [PATCH 0028/1215] method doccomments --- src/lib/foreign-field.ts | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 39d7e76ab2..4f6a2131a8 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -40,6 +40,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { static modulus = p; value: ForeignFieldVar; + /** + * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. + * + * ```ts + * let x = new ForeignField(5); + * ``` + */ constructor(x: ForeignField | ForeignFieldVar | bigint | number | string) { if (x instanceof ForeignField) { this.value = x.value; @@ -54,16 +61,32 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { this.value = ForeignFieldVar.fromBigint(mod(BigInt(x), p)); } + /** + * Coerce the input to a {@link ForeignField}. + */ static from(x: ForeignField | ForeignFieldVar | bigint | number | string) { if (x instanceof ForeignField) return x; return new ForeignField(x); } + /** + * Checks whether this field element is a constant. + * + * See {@link FieldVar} tp understand constants vs variables. + */ isConstant() { let [, ...limbs] = this.value; return limbs.every(FieldVar.isConstant); } + /** + * Convert this field element to a constant. + * + * See {@link FieldVar} tp understand constants vs variables. + * + * **Warning**: This function is only useful in `Provable.witness()` or `Provable.asProver()` blocks, + * that is, in situations where the prover computes a value outside provable code. + */ toConstant(): ForeignField { let [, ...limbs] = this.value; let constantLimbs = mapTuple(limbs, (l) => @@ -72,10 +95,17 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField([0, ...constantLimbs]); } + /** + * Convert this field element to a bigint. + */ toBigInt() { return ForeignFieldVar.toBigint(this.value); } + /** + * Assert that this field element lies in the range [0, p), + * where p is the foreign field modulus. + */ assertValidElement() { if (this.isConstant()) return; Snarky.foreignField.assertValidElement(this.value, pMl); @@ -83,6 +113,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // arithmetic with full constraints, for safe use + /** + * Finite field addition + * + * ```ts + * x.add(2); // x + 2 mod p + * ``` + */ add(y: ForeignField | bigint | number) { if (this.isConstant() && isConstant(y)) { let z = mod(this.toBigInt() + toFp(y), p); @@ -92,6 +129,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField(z); } + /** + * Finite field negation + * + * ```ts + * x.neg(); // -x mod p = p - x + * ``` + */ neg() { if (this.isConstant()) { let x = this.toBigInt(); @@ -102,6 +146,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField(z); } + /** + * Finite field subtraction + * + * ```ts + * x.sub(1); // x - 1 mod p + * ``` + */ sub(y: ForeignField | bigint | number) { if (this.isConstant() && isConstant(y)) { let z = mod(this.toBigInt() - toFp(y), p); @@ -111,6 +162,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField(z); } + /** + * Finite field multiplication + * + * ```ts + * x.mul(y); // x*y mod p + * ``` + */ mul(y: ForeignField | bigint | number) { if (this.isConstant() && isConstant(y)) { let z = mod(this.toBigInt() * toFp(y), p); @@ -120,6 +178,14 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return new ForeignField(z); } + /** + * Multiplicative inverse in the finite field + * + * ```ts + * let z = x.inv(); // 1/x mod p + * z.mul(x).assertEquals(1); + * ``` + */ inv(): ForeignField { if (this.isConstant()) { let z = inverse(this.toBigInt(), p); @@ -144,6 +210,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // convenience methods + /** + * Assert equality with a ForeignField-like value + * + * ```ts + * x.assertEquals(0, "x is zero"); + * ``` + */ assertEquals(y: ForeignField | bigint | number, message?: string) { try { if (this.isConstant() && isConstant(y)) { @@ -159,6 +232,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { } } + /** + * Check equality with a ForeignField-like value + * + * ```ts + * let isXZero = x.equals(0); + * ``` + */ equals(y: ForeignField | bigint | number) { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() === toFp(y)); @@ -168,6 +248,11 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // bit packing + /** + * Unpack a field element to its bits, as a `Bool[]` array. + * + * This method is provable! + */ toBits(length = sizeInBits) { checkBitLength('ForeignField.toBits()', length, sizeInBits); let [l0, l1, l2] = this.toFields(); @@ -182,6 +267,11 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return [...xBits, ...yBits, ...zBits]; } + /** + * Create a field element from its bits, as a `Bool[]` array. + * + * This method is provable! + */ static fromBits(bits: Bool[]) { let length = bits.length; checkBitLength('ForeignField.fromBits()', length, sizeInBits); @@ -197,27 +287,52 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // Provable + /** + * `Provable.toFields`, see {@link Provable.toFields} + */ static toFields(x: ForeignField) { let [, ...limbs] = x.value; return limbs.map((x) => new Field(x)); } + + /** + * Instance version of `Provable.toFields`, see {@link Provable.toFields} + */ toFields() { return ForeignField.toFields(this); } + /** + * `Provable.toAuxiliary`, see {@link Provable.toAuxiliary} + */ static toAuxiliary(): [] { return []; } + /** + * `Provable.sizeInFields`, see {@link Provable.sizeInFields} + */ static sizeInFields() { return 3; } + /** + * `Provable.fromFields`, see {@link Provable.fromFields} + */ static fromFields(fields: Field[]) { let fieldVars = fields.map((x) => x.value); let limbs = arrayToTuple(fieldVars, 3, 'ForeignField.fromFields()'); return new ForeignField([0, ...limbs]); } + /** + * `Provable.check`, see {@link Provable.check} + * + * This will check that the field element is in the range [0, p), + * where p is the foreign field modulus. + * + * Exception: If {@link createForeignField} is called with `{ unsafe: true }`, + * we don't check that field elements are valid by default. + */ static check(x: ForeignField) { // if the `unsafe` flag is set, we don't add any constraints when creating a new variable // this means a user has to take care of proper constraining themselves From 62d22034dbd251a7fc287702aa1841bf7a777041 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 26 Jun 2023 09:43:06 +0200 Subject: [PATCH 0029/1215] add class factory doccomment --- src/lib/foreign-field.ts | 41 +++++++++++++++++++++++++++++- src/lib/foreign-field.unit-test.ts | 13 ++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 4f6a2131a8..6085b62215 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -24,6 +24,45 @@ type ForeignFieldVar = MlForeignField; type ForeignFieldConst = MlForeignField; type ForeignField = InstanceType>; +/** + * Create a class representing a prime order finite field, which is different from the native {@link Field}. + * + * ```ts + * const SmallField = createForeignField(17n); // the finite field F_17 + * ``` + * + * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. + * We support prime moduli up to a size of XXX bits. TODO + * + * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), + * as well as helper methods like `assertEquals()` and `equals()`. + * + * _Advanced usage:_ + * + * Internally, a foreign field element is represented as three native field elements, each of which + * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs + * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. + * With default parameters, new `ForeignField` elements introduced in provable code are automatically + * constrained to be valid on creation. + * + * However, optimized code may want to forgo these automatic checks because in some + * situations they are redundant. Skipping automatic validity checks can be done + * by passing the `unsafe: true` flag: + * + * ```ts + * class UnsafeField extends createForeignField(17n, { unsafe: true }) {} + * ``` + * + * You then often need to manually add validity checks: + * ```ts + * let x: UnsafeField; + * x.assertValidElement(); // prove that x is a valid foreign field element + * ``` + * + * @param modulus the modulus of the finite field you are instantiating + * @param options + * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. + */ function createForeignField(modulus: bigint, { unsafe = false } = {}) { const p = modulus; const pMl = ForeignFieldConst.fromBigint(p); @@ -82,7 +121,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Convert this field element to a constant. * - * See {@link FieldVar} tp understand constants vs variables. + * See {@link FieldVar} to understand constants vs variables. * * **Warning**: This function is only useful in `Provable.witness()` or `Provable.asProver()` blocks, * that is, in situations where the prover computes a value outside provable code. diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index fc9db00f5d..d767b800c2 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -11,10 +11,19 @@ import { ZkProgram } from './proof_system.js'; import { Circuit, circuitMain } from './circuit.js'; import { Scalar } from './scalar.js'; -let ForeignScalar = createForeignField(Fq.modulus); +// toy example - F_17 + +class SmallField extends createForeignField(17n) {} +let x = new SmallField(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// real example - foreign field arithmetic in the Pallas scalar field + +class ForeignScalar extends createForeignField(Fq.modulus) {} // types -ForeignScalar satisfies ProvablePure; +ForeignScalar satisfies ProvablePure; // basic constructor / IO { From 8570541cb4da3d71df437f639c8cdaaf8d7faf8a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 26 Jun 2023 11:28:22 +0200 Subject: [PATCH 0030/1215] check prime modulus size --- src/lib/foreign-field.ts | 30 ++++++++++++++++++++++-------- src/lib/foreign-field.unit-test.ts | 6 ++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 6085b62215..463fa750f5 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,5 +1,5 @@ import { Snarky } from '../snarky.js'; -import { mod, inverse } from '../bindings/crypto/finite_field.js'; +import { mod, inverse, Fp } from '../bindings/crypto/finite_field.js'; import { Tuple } from '../bindings/lib/binable.js'; import { Field, @@ -32,7 +32,7 @@ type ForeignField = InstanceType>; * ``` * * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. - * We support prime moduli up to a size of XXX bits. TODO + * We support prime moduli up to a size of 259 bits. * * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), * as well as helper methods like `assertEquals()` and `equals()`. @@ -67,11 +67,14 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { const p = modulus; const pMl = ForeignFieldConst.fromBigint(p); - // TODO check that p has valid size - // also, maybe check that p is a prime? or does that unnecessarily limit use cases? if (p <= 0) { throw Error(`ForeignField: expected modulus to be positive, got ${p}`); } + if (p > foreignFieldMax) { + throw Error( + `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` + ); + } let sizeInBits = p.toString(2).length; @@ -229,8 +232,12 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { if (this.isConstant()) { let z = inverse(this.toBigInt(), p); if (z === undefined) { - // TODO: if we allow p to be non-prime, change this error message - throw Error('ForeignField.inv(): division by zero'); + if (this.toBigInt() === 0n) { + throw Error('ForeignField.inv(): division by zero'); + } else { + // in case this is used with non-prime moduli + throw Error('ForeignField.inv(): inverse does not exist'); + } } return new ForeignField(z); } @@ -319,7 +326,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { let l1 = Field.fromBits(bits.slice(1 * limbSize, 2 * limbSize)); let l2 = Field.fromBits(bits.slice(2 * limbSize, 3 * limbSize)); let x = ForeignField.fromFields([l0, l1, l2]); - // TODO: this is inefficient, l0, l1 are already range-checked + // TODO: can this be made more efficient? since the limbs are already range-checked if (length === sizeInBits) x.assertValidElement(); return x; } @@ -397,7 +404,14 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // helpers -let limbMax = (1n << limbBits) - 1n; +const limbMax = (1n << limbBits) - 1n; + +// the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus +// see RFC: https://github.com/o1-labs/proof-systems/blob/1fdb1fd1d112f9d4ee095dbb31f008deeb8150b0/book/src/rfcs/foreign_field_mul.md +// since p = 2^254 + eps for both Pasta fields with eps small, a fairly tight lower bound is +// f_max >= sqrt(2^254 * 2^264) = 2^259 +const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * limbBits) / 2n; +const foreignFieldMax = 1n << foreignFieldMaxBits; const ForeignFieldConst = { fromBigint(x: bigint): ForeignFieldConst { diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index d767b800c2..42d2514169 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -18,6 +18,12 @@ let x = new SmallField(16); x.assertEquals(-1); // 16 = -1 (mod 17) x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) +// invalid example - modulus too large + +expect(() => createForeignField(1n << 260n)).toThrow( + 'modulus exceeds the max supported size' +); + // real example - foreign field arithmetic in the Pallas scalar field class ForeignScalar extends createForeignField(Fq.modulus) {} From 3ebd927503124f07de341032891691448ae32e4c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 26 Jun 2023 11:29:00 +0200 Subject: [PATCH 0031/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 0fa6dd0c68..861cfc6f95 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 0fa6dd0c68d2dac8bf4f81bcddf0da6d124f9c0b +Subproject commit 861cfc6f95cae3528b1da45ae5027113e1b858ef From 67eef1fc7ddaac0e71157bb55fd96235e0465ec3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 26 Jun 2023 12:35:17 +0200 Subject: [PATCH 0032/1215] changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d8eb6dd5..d4a9bc73ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/3fbd9678e...HEAD) -> no unreleased changes yet +### Added + +- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 ## [0.11.0](https://github.com/o1-labs/snarkyjs/compare/a632313a...3fbd9678e) From cc72dbfddb9b90523478a233ca4f9342e6765511 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 26 Jun 2023 12:48:30 +0200 Subject: [PATCH 0033/1215] comments --- src/lib/foreign-field.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 463fa750f5..b677b8ca1f 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -467,8 +467,9 @@ function mapTuple, B>( return tuple.map(f) as any; } -// awesome tuple type that has the length as generic parameter - +/** + * tuple type that has the length as generic parameter + */ type TupleN = N extends N ? number extends N ? T[] @@ -478,6 +479,9 @@ type _TupleOf = R['length'] extends N ? R : _TupleOf; +/** + * Type-safe way of converting an array to a fixed-length tuple (same JS representation, but different TS type) + */ function arrayToTuple( arr: E[], size: N, From 8ac409ce4708dfddda7d0f4ebbc047b483cb10ae Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 27 Jun 2023 00:05:37 +0200 Subject: [PATCH 0034/1215] Apply suggestions from code review Co-authored-by: Martin Minkov --- src/lib/foreign-field.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index b677b8ca1f..ed12de7669 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -84,7 +84,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. - * + * @example * ```ts * let x = new ForeignField(5); * ``` @@ -114,7 +114,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Checks whether this field element is a constant. * - * See {@link FieldVar} tp understand constants vs variables. + * See {@link FieldVar} to understand constants vs variables. */ isConstant() { let [, ...limbs] = this.value; @@ -126,7 +126,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * * See {@link FieldVar} to understand constants vs variables. * - * **Warning**: This function is only useful in `Provable.witness()` or `Provable.asProver()` blocks, + * **Warning**: This function is only useful in {@link Provable.witness} or {@link Provable.asProver} blocks, * that is, in situations where the prover computes a value outside provable code. */ toConstant(): ForeignField { @@ -157,7 +157,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Finite field addition - * + * @example * ```ts * x.add(2); // x + 2 mod p * ``` @@ -173,7 +173,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Finite field negation - * + * @example * ```ts * x.neg(); // -x mod p = p - x * ``` @@ -206,7 +206,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Finite field multiplication - * + * @example * ```ts * x.mul(y); // x*y mod p * ``` @@ -222,7 +222,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Multiplicative inverse in the finite field - * + * @example * ```ts * let z = x.inv(); // 1/x mod p * z.mul(x).assertEquals(1); @@ -258,7 +258,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Assert equality with a ForeignField-like value - * + * @example * ```ts * x.assertEquals(0, "x is zero"); * ``` @@ -280,7 +280,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Check equality with a ForeignField-like value - * + * @example * ```ts * let isXZero = x.equals(0); * ``` @@ -295,7 +295,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // bit packing /** - * Unpack a field element to its bits, as a `Bool[]` array. + * Unpack a field element to its bits, as a {@link Bool}[] array. * * This method is provable! */ @@ -376,7 +376,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * This will check that the field element is in the range [0, p), * where p is the foreign field modulus. * - * Exception: If {@link createForeignField} is called with `{ unsafe: true }`, + * **Exception**: If {@link createForeignField} is called with `{ unsafe: true }`, * we don't check that field elements are valid by default. */ static check(x: ForeignField) { From 0179f2b96c59e406fda4a38100dfa53b6ce57f1f Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 27 Jun 2023 00:06:27 +0200 Subject: [PATCH 0035/1215] Update src/lib/foreign-field.ts Co-authored-by: Martin Minkov --- src/lib/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index ed12de7669..a34b476d86 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -190,7 +190,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { /** * Finite field subtraction - * + * @example * ```ts * x.sub(1); // x - 1 mod p * ``` From 43fb1822851ff334ae8529d1650b800126f49dcc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 26 Jun 2023 15:31:09 -0700 Subject: [PATCH 0036/1215] feat(bindings): update bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 0202843678..a92e783112 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 0202843678471768ea70b53f21306186f782677c +Subproject commit a92e7831122164e67e0374b8aac893d738ff6626 From 20d4eb733406611569f33e644f4f5b276439d894 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 26 Jun 2023 15:33:04 -0700 Subject: [PATCH 0037/1215] refactor(hash.ts): remove unnecessary check for empty message in buildSHA function --- src/lib/hash.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 3368201e52..a93234ae92 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -198,10 +198,6 @@ function emptyReceiptChainHash() { function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { hash(message: (Field | UInt8)[]) { - if (message.length === 0) { - throw Error('SHA hash of empty message'); - } - const values = message.map((f) => { if (isField(f)) { // Make sure that the field is exactly a byte. From 93a71797e8a3bf997c1ed4076c43cdd524dfdce2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 27 Jun 2023 15:09:26 +0200 Subject: [PATCH 0038/1215] add sum() and reimplement add and sub with it --- src/bindings | 2 +- src/lib/foreign-field.ts | 64 +++++++++++++++++++++++++++++----------- src/snarky.d.ts | 11 ++----- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/bindings b/src/bindings index 861cfc6f95..a68cec4f87 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 861cfc6f95cae3528b1da45ae5027113e1b858ef +Subproject commit a68cec4f8736cc16cbb7271ca52835d11ea3430b diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index a34b476d86..8d0ce8cfeb 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -10,6 +10,7 @@ import { } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; +import { MlArray } from './ml/base.js'; // external API export { createForeignField, ForeignField }; @@ -82,6 +83,8 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { static modulus = p; value: ForeignFieldVar; + static #zero = new ForeignField(0); + /** * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. * @example @@ -163,12 +166,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ``` */ add(y: ForeignField | bigint | number) { - if (this.isConstant() && isConstant(y)) { - let z = mod(this.toBigInt() + toFp(y), p); - return new ForeignField(z); - } - let z = Snarky.foreignField.add(this.value, toVar(y), pMl); - return new ForeignField(z); + return ForeignField.sum([this, y], [1]); } /** @@ -179,13 +177,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ``` */ neg() { - if (this.isConstant()) { - let x = this.toBigInt(); - let z = x === 0n ? 0n : p - x; - return new ForeignField(z); - } - let z = Snarky.foreignField.sub(ForeignFieldVar[0], this.value, pMl); - return new ForeignField(z); + return ForeignField.sum([ForeignField.#zero, this], [-1]); } /** @@ -196,11 +188,44 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ``` */ sub(y: ForeignField | bigint | number) { - if (this.isConstant() && isConstant(y)) { - let z = mod(this.toBigInt() - toFp(y), p); - return new ForeignField(z); + return ForeignField.sum([this, y], [-1]); + } + + /** + * Sum (or difference) of multiple finite field elements. + * + * @example + * ```ts + * let z = ForeignField.sum([3, 2, 1], [-1, 1]); // 3 - 2 + 1 + * z.assertEquals(2); + * ``` + * + * This method expects a list of ForeignField-like values, `x0,...,xn`, + * and a list of "operations" `op1,...,opn` where every op is 1 or -1 (plus or minus), + * and returns + * + * `x0 + op1*x1 + ... + opn*xn` + * + * where the sum is computed in finite field arithmetic. + * + * **Important:** For more than two summands, this is significantly more efficient + * than chaining calls to {@link ForeignField.add} and {@link ForeignField.sub}. + * + */ + static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { + if (xs.every(isConstant)) { + let sum = xs.reduce((sum: bigint, x, i): bigint => { + if (i === 0) return toFp(x); + return sum + BigInt(operations[i - 1]) * toFp(x); + }, 0n); + // note: we don't reduce mod p because the constructor does that + return new ForeignField(sum); } - let z = Snarky.foreignField.sub(this.value, toVar(y), pMl); + let fields = MlArray.to(xs.map(toVar)); + let opModes = MlArray.to( + operations.map((op) => (op === 1 ? OpMode.Add : OpMode.Sub)) + ); + let z = Snarky.foreignField.sumChain(fields, opModes, pMl); return new ForeignField(z); } @@ -402,6 +427,11 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return ForeignField; } +enum OpMode { + Add, + Sub, +} + // helpers const limbMax = (1n << limbBits) - 1n; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 9bad8ee2be..48ce4e58e3 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -271,14 +271,9 @@ declare const Snarky: { foreignField: { assertValidElement(x: ForeignFieldVar, p: ForeignFieldConst): void; - add( - x: ForeignFieldVar, - y: ForeignFieldVar, - p: ForeignFieldConst - ): ForeignFieldVar; - sub( - x: ForeignFieldVar, - y: ForeignFieldVar, + sumChain( + xs: MlArray, + ops: MlArray<0 | 1>, p: ForeignFieldConst ): ForeignFieldVar; mul( From 8016ad9b846cb6149e995e3cc8e08f1f0d3312e2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 27 Jun 2023 15:09:54 +0200 Subject: [PATCH 0039/1215] add test for sum() including checking constraints --- src/lib/foreign-field.unit-test.ts | 91 +++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 42d2514169..e2a6418a08 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,6 +1,6 @@ import { ProvablePure } from '../snarky.js'; import { Group } from './core.js'; -import { FieldVar } from './field.js'; +import { Field, FieldVar } from './field.js'; import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; @@ -85,6 +85,88 @@ equivalent1( Random.scalar ); +// test random sum chains up to length 20 + +test( + Random.array( + Random.record({ + scalar: Random.scalar, + operation: Random.oneOf<[1, -1]>(1, -1), + }), + Random.nat(20) + ), + (sumSpec) => { + if (sumSpec.length === 0) return; + + let scalars = sumSpec.map((s) => s.scalar); + let operations = sumSpec.slice(1).map((s) => s.operation); + let functions = operations.map((op) => (op === 1 ? Fq.add : Fq.sub)); + + // compute sum on bigints + let sum = scalars.reduce( + (sum, s, i) => (i === 0 ? s : functions[i - 1](sum, s)), + 0n + ); + + // check that the expected sum is computed in provable code + + function main() { + let scalarVars = scalars.map((s) => + Provable.witness(ForeignScalar, () => new ForeignScalar(s)) + ); + let z = ForeignScalar.sum(scalarVars, operations); + Provable.asProver(() => expect(z.toBigInt()).toEqual(sum)); + } + + Provable.runAndCheck(main); + + // check that the expected gates are created + + let expectedGateTypes: GateType[] = []; + + let boundsCheck: GateType[] = [ + 'ForeignFieldAdd', + 'Zero', + 'RangeCheck0', + 'RangeCheck0', + 'RangeCheck1', + 'Zero', + ]; + + // for every witnessed scalar, add gates for the bounds check + scalars.forEach(() => expectedGateTypes.push(...boundsCheck)); + + // now, add as many ForeignFieldAdd gates as there are additions + operations.forEach(() => expectedGateTypes.push('ForeignFieldAdd')); + + // add a final bound check for the result + expectedGateTypes.push(...boundsCheck); + + // compute the actual gates + let { gates } = Provable.constraintSystem(main); + + // split out all generic gates + let generics = gates.filter((g) => g.type === 'Generic'); + gates = gates.filter((g) => g.type !== 'Generic'); + let gateTypes = gates.map((g) => g.type); + + // check that gates without generics are as expected + expect(gateTypes).toEqual(expectedGateTypes); + + // check that generic gates correspond to adding one of the constants 0, 1 and 2^88 (the limb size) + let allowedConstants = new Set([0n, 1n, 1n << 88n]); + let ok = generics.every(({ coeffs: [left, right, out, mul, constant] }) => { + let isConstantGate = + ((left === '0' && right === '1') || (left === '1' && right === '0')) && + out === '0' && + mul === '0'; + let constantValue = Field.ORDER - BigInt(constant); + return isConstantGate && allowedConstants.has(constantValue); + }); + expect(ok).toBe(true); + } +); + // scalar shift in foreign field arithmetic vs in the exponent let scalarShift = Fq(1n + 2n ** 255n); @@ -184,3 +266,10 @@ let proof = await Program.test(); ok = await Program.verify(proof); console.log('verifies?', ok); */ + +type GateType = + | 'Zero' + | 'Generic' + | 'RangeCheck0' + | 'RangeCheck1' + | 'ForeignFieldAdd'; From 2d58c7ee7cc028622de8f3a0f70f8ec5748132f9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 09:32:02 -0700 Subject: [PATCH 0040/1215] refactor(hash.ts): remove Field type from buildSHA --- src/examples/keccak.ts | 10 +++++----- src/lib/hash.ts | 15 ++++----------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 15b582d880..171b76acfc 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -2,25 +2,25 @@ import { Hash, Field, Provable, UInt8 } from 'snarkyjs'; console.log('Running SHA224 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA224([Field(1), Field(30000), new UInt8(2)]); + let digest = Hash.SHA224([new UInt8(1), new UInt8(2), new UInt8(3)]); Provable.log(digest); }); console.log('Running SHA256 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA256([Field(1), Field(1), new UInt8(2)]); + let digest = Hash.SHA256([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); console.log('Running SHA384 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA384([Field(1), Field(1), new UInt8(2)]); + let digest = Hash.SHA384([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); console.log('Running SHA512 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA512([Field(1), Field(1), new UInt8(2)]); + let digest = Hash.SHA512([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); @@ -38,6 +38,6 @@ Provable.runAndCheck(() => { console.log('Running keccack hash test'); Provable.runAndCheck(() => { - let digest = Hash.Keccack256([Field(1), Field(1), new UInt8(2)]); + let digest = Hash.Keccack256([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index a93234ae92..b77a0bc6be 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -197,17 +197,10 @@ function emptyReceiptChainHash() { function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { - hash(message: (Field | UInt8)[]) { - const values = message.map((f) => { - if (isField(f)) { - // Make sure that the field is exactly a byte. - f.toBits(8); - return f.value; - } - return f.value.value; - }); - - return Snarky.sha.create([0, ...values], nist, length).map(Field); + hash(message: UInt8[]) { + return Snarky.sha + .create([0, ...message.map((f) => f.value.value)], nist, length) + .map(Field); }, }; } From 10bda74c0818767d78bd10bd8ab7b061eea2d943 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 09:47:52 -0700 Subject: [PATCH 0041/1215] refactor(hash.ts): return hash function for hash objects --- src/examples/keccak.ts | 12 ++++++------ src/lib/hash.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 171b76acfc..004ae436df 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -2,31 +2,31 @@ import { Hash, Field, Provable, UInt8 } from 'snarkyjs'; console.log('Running SHA224 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA224([new UInt8(1), new UInt8(2), new UInt8(3)]); + let digest = Hash.SHA224.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); Provable.log(digest); }); console.log('Running SHA256 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA256([new UInt8(1), new UInt8(1), new UInt8(2)]); + let digest = Hash.SHA256.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); console.log('Running SHA384 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA384([new UInt8(1), new UInt8(1), new UInt8(2)]); + let digest = Hash.SHA384.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); console.log('Running SHA512 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA512([new UInt8(1), new UInt8(1), new UInt8(2)]); + let digest = Hash.SHA512.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); console.log('Running Poseidon test'); Provable.runAndCheck(() => { - let digest = Hash.Poseidon([Field(1), Field(1), Field(2)]); + let digest = Hash.Poseidon.hash([Field(1), Field(1), Field(2)]); Provable.log(digest); }); @@ -38,6 +38,6 @@ Provable.runAndCheck(() => { console.log('Running keccack hash test'); Provable.runAndCheck(() => { - let digest = Hash.Keccack256([new UInt8(1), new UInt8(1), new UInt8(2)]); + let digest = Hash.Keccack256.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); Provable.log(digest); }); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index b77a0bc6be..afb83112ef 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -208,15 +208,15 @@ function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { const Hash = { default: Poseidon.hash, - Poseidon: Poseidon.hash, + Poseidon: Poseidon, - SHA224: buildSHA(224, true).hash, + SHA224: buildSHA(224, true), - SHA256: buildSHA(256, true).hash, + SHA256: buildSHA(256, true), - SHA384: buildSHA(384, true).hash, + SHA384: buildSHA(384, true), - SHA512: buildSHA(512, true).hash, + SHA512: buildSHA(512, true), - Keccack256: buildSHA(256, false).hash, + Keccack256: buildSHA(256, false), }; From 49a294bb8ebdcc2c8eab64c698809716c1f281df Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 09:48:43 -0700 Subject: [PATCH 0042/1215] refactor(keccak.ts): rename Hash.default to Hash.hash --- src/examples/keccak.ts | 2 +- src/lib/hash.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 004ae436df..b794772d29 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -32,7 +32,7 @@ Provable.runAndCheck(() => { console.log('Running default hash test'); Provable.runAndCheck(() => { - let digest = Hash.default([Field(1), Field(1), Field(2)]); + let digest = Hash.hash([Field(1), Field(1), Field(2)]); Provable.log(digest); }); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index afb83112ef..c4039662fb 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -206,7 +206,7 @@ function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { } const Hash = { - default: Poseidon.hash, + hash: Poseidon.hash, Poseidon: Poseidon, From 22977f58bb6c7101f2a20b46a9ed32721ce2b4d3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 10:00:35 -0700 Subject: [PATCH 0043/1215] refactor(hash.ts): change buildSHA function to return a UInt8 array instead of a Field array --- src/lib/hash.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index c4039662fb..853006b9f3 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -197,10 +197,10 @@ function emptyReceiptChainHash() { function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { - hash(message: UInt8[]) { + hash(message: UInt8[]): UInt8[] { return Snarky.sha .create([0, ...message.map((f) => f.value.value)], nist, length) - .map(Field); + .map((f) => new UInt8(Field(f))); }, }; } From 42d9f74d56685312e28e60f304d6dae216c257a9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 10:08:15 -0700 Subject: [PATCH 0044/1215] feat(int.ts): add fromFields to UInt8 --- src/lib/int.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index fca2b6c971..2455a2f3e1 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -965,12 +965,14 @@ class UInt8 extends Struct({ }) { constructor(x: number | Field) { super({ value: Field(x) }); - - // Make sure that the Field element that is exactly a byte - this.value.toBits(8); + this.value.toBits(8); // Make sure that the Field element that is exactly a byte } check() { this.value.toBits(8); } + + fromFields(xs: Field[]): UInt8[] { + return xs.map((x) => new UInt8(x)); + } } From 957c1f8e3818d66dd617c960d5ae1f4c88f36d30 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 10:10:59 -0700 Subject: [PATCH 0045/1215] feat(int.ts): add fromHex method to UInt8 class to convert hex string to UInt8 array using Snarky.sha library --- src/lib/int.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 2455a2f3e1..f22b6033fc 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,6 +3,7 @@ import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; +import { Snarky } from 'src/snarky.js'; // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -975,4 +976,8 @@ class UInt8 extends Struct({ fromFields(xs: Field[]): UInt8[] { return xs.map((x) => new UInt8(x)); } + + static fromHex(xs: string): UInt8[] { + return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); + } } From ae01aa777d162f09a1fea192e4a4d8a360dc97e4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 16:01:36 -0700 Subject: [PATCH 0046/1215] feat(int.ts): add static method toHex to UInt8 class to convert an array of UInt8 to a hex string --- src/lib/int.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index f22b6033fc..bf935e03a9 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,7 +3,7 @@ import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { Snarky } from 'src/snarky.js'; +import { Snarky } from '../snarky.js'; // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -980,4 +980,20 @@ class UInt8 extends Struct({ static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } + static toHex(xs: UInt8[]): string { + return xs + .map((x) => x.value) + .map((value) => byteArrayToHex(Field.toBytes(value))) + .join(''); + } +} + +// TODO: Move to more appropriate place? +function byteArrayToHex(byteArray: number[]): string { + return byteArray + .map((byte) => { + const hexValue = byte.toString(16).padStart(2, '0'); + return hexValue === '00' ? '' : hexValue; + }) + .join(''); } From 92deb9e7a2c46a22498844d86dfa8747482a5d14 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 27 Jun 2023 16:01:53 -0700 Subject: [PATCH 0047/1215] test(keccak.ts): add tests for checking hex->digest, digest->hex conversion for SHA224, SHA256, SHA384, SHA512, and Keccack256 hash functions. Add helper functions to check the conversion and compare the digests. --- src/examples/keccak.ts | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index b794772d29..818aff372c 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -1,43 +1,66 @@ -import { Hash, Field, Provable, UInt8 } from 'snarkyjs'; +import { Field, Provable, Hash, UInt8 } from 'snarkyjs'; + +function equals(a: UInt8[], b: UInt8[]): boolean { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) + if (a[i].value.toConstant() === b[i].value.toConstant()) return false; + + return true; +} + +function checkDigestHexConversion(digest: UInt8[]) { + console.log('Checking hex->digest, digest->hex matches'); + Provable.asProver(() => { + const hex = UInt8.toHex(digest); + const expected = UInt8.fromHex(hex); + Provable.log(hex, digest, expected); + if (equals(digest, expected)) { + console.log('✅ Digest matches'); + } else { + console.log('❌ Digest does not match'); + } + }); +} console.log('Running SHA224 test'); Provable.runAndCheck(() => { let digest = Hash.SHA224.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - Provable.log(digest); + checkDigestHexConversion(digest); }); console.log('Running SHA256 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA256.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); - Provable.log(digest); + let digest = Hash.SHA256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); + checkDigestHexConversion(digest); }); console.log('Running SHA384 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA384.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); - Provable.log(digest); + let digest = Hash.SHA384.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); + checkDigestHexConversion(digest); }); +// TODO: This test fails console.log('Running SHA512 test'); Provable.runAndCheck(() => { - let digest = Hash.SHA512.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); - Provable.log(digest); + let digest = Hash.SHA512.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); + checkDigestHexConversion(digest); }); -console.log('Running Poseidon test'); +console.log('Running keccack hash test'); Provable.runAndCheck(() => { - let digest = Hash.Poseidon.hash([Field(1), Field(1), Field(2)]); - Provable.log(digest); + let digest = Hash.Keccack256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); + checkDigestHexConversion(digest); }); -console.log('Running default hash test'); +console.log('Running Poseidon test'); Provable.runAndCheck(() => { - let digest = Hash.hash([Field(1), Field(1), Field(2)]); + let digest = Hash.Poseidon.hash([Field(1), Field(2), Field(3)]); Provable.log(digest); }); -console.log('Running keccack hash test'); +console.log('Running default hash test'); Provable.runAndCheck(() => { - let digest = Hash.Keccack256.hash([new UInt8(1), new UInt8(1), new UInt8(2)]); + let digest = Hash.hash([Field(1), Field(2), Field(3)]); Provable.log(digest); }); From a637e7cdd5a3589efead01ef6ab50e41037885a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 28 Jun 2023 13:07:11 +0200 Subject: [PATCH 0048/1215] fix example --- src/examples/api_exploration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/api_exploration.ts b/src/examples/api_exploration.ts index 427e37065d..6bd76e5e2f 100644 --- a/src/examples/api_exploration.ts +++ b/src/examples/api_exploration.ts @@ -149,8 +149,8 @@ console.assert(!signature.verify(pubKey, msg1).toBoolean()); */ /* You can initialize elements as literals as follows: */ -let g0 = new Group(-1, 2); -let g1 = new Group({ x: -2, y: 2 }); +let g0 = Group.from(-1, 2); +let g1 = new Group({ x: -1, y: 2 }); /* There is also a predefined generator. */ let g2 = Group.generator; From 1febfbeb16d15cac81343f6d0319c9cc3da7d24f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 28 Jun 2023 17:37:23 +0200 Subject: [PATCH 0049/1215] initial minimal foreign curve, doesn't work yet --- src/bindings | 2 +- src/lib/foreign-curve.ts | 118 +++++++++++++++++++++++++++++++++++++++ src/lib/ml/base.ts | 21 ++++++- src/snarky.d.ts | 14 +++++ 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/lib/foreign-curve.ts diff --git a/src/bindings b/src/bindings index a68cec4f87..ecca66043d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a68cec4f8736cc16cbb7271ca52835d11ea3430b +Subproject commit ecca66043d25f346b17cab6993af5e5ed843663d diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts new file mode 100644 index 0000000000..05bbff8f86 --- /dev/null +++ b/src/lib/foreign-curve.ts @@ -0,0 +1,118 @@ +import { Snarky } from '../snarky.js'; +import { + ForeignField, + ForeignFieldConst, + ForeignFieldVar, + createForeignField, +} from './foreign-field.js'; +import { MlBigint } from './ml/base.js'; + +// external API +export { createForeignCurve }; + +// internal API +export { ForeignCurveVar, ForeignCurveConst, MlCurveParams }; + +type MlAffine = [_: 0, x: F, y: F]; +type ForeignCurveVar = MlAffine; +type ForeignCurveConst = MlAffine; + +type AffineBigint = { x: bigint; y: bigint }; +type Affine = { x: ForeignField; y: ForeignField }; + +function createForeignCurve(curve: CurveParams) { + const curveMl = MlCurveParams(curve); + + class BaseField extends createForeignField(curve.modulus) {} + class ScalarField extends createForeignField(curve.order) {} + + function toMl({ x, y }: Affine): ForeignCurveVar { + return [0, x.value, y.value]; + } + + class ForeignCurve implements Affine { + x: BaseField; + y: BaseField; + + constructor( + g: + | { x: BaseField | bigint | number; y: BaseField | bigint | number } + | ForeignCurveVar + ) { + // ForeignCurveVar + if (Array.isArray(g)) { + let [, x, y] = g; + this.x = new BaseField(x); + this.y = new BaseField(y); + return; + } + let { x, y } = g; + this.x = BaseField.from(x); + this.y = BaseField.from(y); + } + + add(h: ForeignCurve) { + let p = Snarky.foreignField.curve.add(toMl(this), toMl(h), curveMl); + return new ForeignCurve(p); + } + + static BaseField = BaseField; + static ScalarField = ScalarField; + } + + return ForeignCurve; +} + +/** + * Parameters defining an elliptic curve in short Weierstraß form + * y^2 = x^3 + ax + b + */ +type CurveParams = { + /** + * Base field modulus + */ + modulus: bigint; + /** + * Scalar field modulus = group order + */ + order: bigint; + /** + * The `a` parameter in the curve equation y^2 = x^3 + ax + b + */ + a: bigint; + /** + * The `b` parameter in the curve equation y^2 = x^3 + ax + b + */ + b: bigint; + /** + * Generator point + */ + gen: AffineBigint; +}; + +type MlBigintPoint = MlAffine; + +function MlBigintPoint({ x, y }: AffineBigint): MlBigintPoint { + return [0, MlBigint(x), MlBigint(y)]; +} + +type MlCurveParams = [ + _: 0, + modulus: MlBigint, + order: MlBigint, + a: MlBigint, + b: MlBigint, + gen: MlBigintPoint +]; + +function MlCurveParams(params: CurveParams): MlCurveParams { + let { modulus, order, a, b, gen } = params; + return [ + 0, + MlBigint(modulus), + MlBigint(order), + MlBigint(a), + MlBigint(b), + MlBigintPoint(gen), + ]; +} diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 89de7b01bc..4bdddda154 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -1,7 +1,9 @@ +import { Snarky } from '../../snarky.js'; + /** * This module contains basic methods for interacting with OCaml */ -export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes }; +export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes, MlBigint }; // ocaml types @@ -53,3 +55,20 @@ const MlBool = Object.assign( }, } ); + +/** + * zarith_stubs_js representation of a bigint / Zarith.t, which + * is what Snarky_backendless.Backend_extended.Bignum_bigint.t is under the hood + */ +type MlBigint = number | { value: bigint; caml_custom: '_z' }; + +const MlBigint = Object.assign( + function MlBigint(x: bigint): MlBigint { + return Snarky.foreignField.bigintToMl(x); + }, + { + from(x: MlBigint) { + return typeof x === 'number' ? BigInt(x) : x.value; + }, + } +); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 48ce4e58e3..a723f73f4f 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -9,12 +9,14 @@ import type { MlOption, MlBool, MlBytes, + MlBigint, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; import type { ForeignFieldVar, ForeignFieldConst, } from './lib/foreign-field.js'; +import type { ForeignCurveVar, MlCurveParams } from './lib/foreign-curve.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; @@ -281,6 +283,18 @@ declare const Snarky: { y: ForeignFieldVar, p: ForeignFieldConst ): ForeignFieldVar; + + bigintToMl(x: bigint): MlBigint; + + curve: { + create(params: MlCurveParams): unknown; + // paramsToVars() + add( + g: ForeignCurveVar, + h: ForeignCurveVar, + curveParams: unknown + ): ForeignCurveVar; + }; }; }; From e012ba6bdc9440e1f16d5e1708d1fdb533287b46 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 28 Jun 2023 17:37:34 +0200 Subject: [PATCH 0050/1215] initial work on test --- src/lib/foreign-curve.unit-test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/lib/foreign-curve.unit-test.ts diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts new file mode 100644 index 0000000000..b78ebf688e --- /dev/null +++ b/src/lib/foreign-curve.unit-test.ts @@ -0,0 +1,21 @@ +import { createForeignCurve } from './foreign-curve.js'; +import { Fp, Fq } from '../bindings/crypto/finite_field.js'; +import { Vesta as VestaBigint } from '../bindings/crypto/elliptic_curve.js'; +import { Provable } from './provable.js'; + +class Vesta extends createForeignCurve({ + modulus: Fq.modulus, + order: Fp.modulus, + a: 0n, + b: VestaBigint.b, + gen: VestaBigint.one, +}) {} + +let g = { x: Fq.negate(1n), y: 2n, infinity: false }; +let gPlusOne = VestaBigint.toAffine( + VestaBigint.add(VestaBigint.fromAffine(g), VestaBigint.one) +); + +// Provable.runAndCheck(() => { +// let g0 = Provable.witness(Vesta, () => new Vesta(g)); +// }); From b600b2805d546fb4bd9d2b72908133592cb6e3b9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 09:27:28 -0700 Subject: [PATCH 0051/1215] fix(int.ts): working impl of toHex for UInt8 --- src/examples/keccak.ts | 2 +- src/lib/int.ts | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 818aff372c..1fcf7557fa 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -13,10 +13,10 @@ function checkDigestHexConversion(digest: UInt8[]) { Provable.asProver(() => { const hex = UInt8.toHex(digest); const expected = UInt8.fromHex(hex); - Provable.log(hex, digest, expected); if (equals(digest, expected)) { console.log('✅ Digest matches'); } else { + Provable.log(`hex: ${hex}\ndigest: ${digest}\nexpected:${expected}`); console.log('❌ Digest does not match'); } }); diff --git a/src/lib/int.ts b/src/lib/int.ts index bf935e03a9..acc91558fa 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -980,20 +980,12 @@ class UInt8 extends Struct({ static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } + static toHex(xs: UInt8[]): string { return xs .map((x) => x.value) - .map((value) => byteArrayToHex(Field.toBytes(value))) + .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) + .slice(1) .join(''); } } - -// TODO: Move to more appropriate place? -function byteArrayToHex(byteArray: number[]): string { - return byteArray - .map((byte) => { - const hexValue = byte.toString(16).padStart(2, '0'); - return hexValue === '00' ? '' : hexValue; - }) - .join(''); -} From ecd62c4755b5096a62a1aad998654fce4829377d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:09:10 -0700 Subject: [PATCH 0052/1215] feat(random.ts): add support for generating random UInt8 values --- src/lib/testing/random.ts | 14 +++++++++++--- src/provable/field-bigint.ts | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index f9d1919502..74ffe3e32d 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -63,6 +63,7 @@ function sample(rng: Random, size: number) { const boolean = Random_(() => drawOneOf8() < 4); const bool = map(boolean, Bool); +const uint8 = biguintWithInvalid(8); const uint32 = biguintWithInvalid(32); const uint64 = biguintWithInvalid(64); @@ -115,6 +116,7 @@ type Generators = { const Generators: Generators = { Field: field, Bool: bool, + UInt8: uint8, UInt32: uint32, UInt64: uint64, Sign: sign, @@ -181,17 +183,21 @@ const nonNumericString = reject( string(nat(20)), (str: any) => !isNaN(str) && !isNaN(parseFloat(str)) ); -const invalidUint64Json = toString( - oneOf(uint64.invalid, nonInteger, nonNumericString) +const invalidUint8Json = toString( + oneOf(uint8.invalid, nonInteger, nonNumericString) ); const invalidUint32Json = toString( oneOf(uint32.invalid, nonInteger, nonNumericString) ); +const invalidUint64Json = toString( + oneOf(uint64.invalid, nonInteger, nonNumericString) +); // some json versions of those types let json_ = { - uint64: { ...toString(uint64), invalid: invalidUint64Json }, + uint8: { ...toString(uint8), invalid: invalidUint8Json }, uint32: { ...toString(uint32), invalid: invalidUint32Json }, + uint64: { ...toString(uint64), invalid: invalidUint64Json }, publicKey: withInvalidBase58(mapWithInvalid(publicKey, PublicKey.toBase58)), privateKey: withInvalidBase58(map(privateKey, PrivateKey.toBase58)), keypair: map(keypair, ({ privatekey, publicKey }) => ({ @@ -215,6 +221,7 @@ type JsonGenerators = { const JsonGenerators: JsonGenerators = { Field: json_.field, Bool: boolean, + UInt8: json_.uint8, UInt32: json_.uint32, UInt64: json_.uint64, Sign: withInvalidRandomString(map(sign, Sign.toJSON)), @@ -299,6 +306,7 @@ const Random = Object.assign(Random_, { dice: Object.assign(dice, { ofSize: diceOfSize() }), field, bool, + uint8, uint32, uint64, privateKey, diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index ea2d797c83..769f31dab7 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -6,11 +6,12 @@ import { ProvableBigint, } from '../bindings/lib/provable-bigint.js'; -export { Field, Bool, UInt32, UInt64, Sign }; +export { Field, Bool, UInt8, UInt32, UInt64, Sign }; export { pseudoClass, sizeInBits, checkRange, checkField }; type Field = bigint; type Bool = 0n | 1n; +type UInt8 = bigint; type UInt32 = bigint; type UInt64 = bigint; @@ -99,6 +100,7 @@ function Unsigned(bits: number) { } ); } +const UInt8 = Unsigned(8); const UInt32 = Unsigned(32); const UInt64 = Unsigned(64); From 8bd3a93ee41d29c342f1e86a1f55d1489d46969d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:16:56 -0700 Subject: [PATCH 0053/1215] feat(int.ts): add bigint to constructor and introduce static NUM_BITS --- src/lib/int.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index acc91558fa..99e225e9fa 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -964,13 +964,15 @@ class Int64 extends CircuitValue implements BalanceChange { class UInt8 extends Struct({ value: Field, }) { - constructor(x: number | Field) { + static NUM_BITS = 8; + + constructor(x: number | bigint | Field) { super({ value: Field(x) }); - this.value.toBits(8); // Make sure that the Field element that is exactly a byte + this.value.toBits(UInt8.NUM_BITS); // Make sure that the Field element that is exactly a byte } check() { - this.value.toBits(8); + this.value.toBits(UInt8.NUM_BITS); } fromFields(xs: Field[]): UInt8[] { From cd6a019410b30c87971b9e9e00d4ad28e66818f8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:18:03 -0700 Subject: [PATCH 0054/1215] feat(int.ts): add static properties and to UInt8 class --- src/lib/int.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 99e225e9fa..75f28c11f9 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -971,6 +971,14 @@ class UInt8 extends Struct({ this.value.toBits(UInt8.NUM_BITS); // Make sure that the Field element that is exactly a byte } + static get zero() { + return new UInt8(0); + } + + static get one() { + return new UInt8(1); + } + check() { this.value.toBits(UInt8.NUM_BITS); } From 7fed25698ddd929c54b3b5c5c93cb2db99b6a438 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:19:57 -0700 Subject: [PATCH 0055/1215] chore(int.ts): add toString(), toBigInt(), toField(), and toJSON() methods to UInt8 class for better usability and serialization --- src/lib/int.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 75f28c11f9..7d27f95180 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -979,6 +979,18 @@ class UInt8 extends Struct({ return new UInt8(1); } + toString() { + return this.value.toString(); + } + + toBigInt() { + return this.value.toBigInt(); + } + + toField() { + return this.value; + } + check() { this.value.toBits(UInt8.NUM_BITS); } @@ -998,4 +1010,8 @@ class UInt8 extends Struct({ .slice(1) .join(''); } + + static toJSON(xs: UInt8): string { + return xs.value.toString(); + } } From 8a9718970b9ba4e5dfbb7b3245e8fd08da52f022 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:21:01 -0700 Subject: [PATCH 0056/1215] chore(int.ts): add static method MAXINT() to UInt8 class to return the maximum value of UInt8 --- src/lib/int.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 7d27f95180..c36440df1c 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1014,4 +1014,8 @@ class UInt8 extends Struct({ static toJSON(xs: UInt8): string { return xs.value.toString(); } + + static MAXINT() { + return new UInt8(255); + } } From 7176fb6ee4783bbf085d655831c36a3e402a5652 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:21:59 -0700 Subject: [PATCH 0057/1215] refactor(int.ts): change static method to instance method --- src/lib/int.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index c36440df1c..df18f68a22 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1011,8 +1011,8 @@ class UInt8 extends Struct({ .join(''); } - static toJSON(xs: UInt8): string { - return xs.value.toString(); + toJSON(): string { + return this.value.toString(); } static MAXINT() { From 3c1bc9454a8a2a51984938d1885988e9f95614af Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:25:15 -0700 Subject: [PATCH 0058/1215] feat(int.ts): add methods toJSON, toUInt32, and toUInt64 to the UInt8 class --- src/lib/int.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index df18f68a22..35ddfb01ae 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -999,6 +999,18 @@ class UInt8 extends Struct({ return xs.map((x) => new UInt8(x)); } + toJSON(): string { + return this.value.toString(); + } + + toUInt32(): UInt32 { + return new UInt32(this.value); + } + + toUInt64(): UInt64 { + return new UInt64(this.value); + } + static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } From e15f7e2b23bcd3604403395eae9c5c1551783c0f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:25:57 -0700 Subject: [PATCH 0059/1215] feat(int.ts): add from and private checkConstant methods --- src/lib/int.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 35ddfb01ae..1eb5e7b16b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1023,11 +1023,20 @@ class UInt8 extends Struct({ .join(''); } - toJSON(): string { - return this.value.toString(); - } - static MAXINT() { return new UInt8(255); } + + static from(x: UInt64 | UInt32 | Field | number | string | bigint) { + if (x instanceof UInt64 || x instanceof UInt32 || x instanceof UInt8) + x = x.value; + + return new this(this.checkConstant(Field(x))); + } + + private static checkConstant(x: Field) { + if (!x.isConstant()) return x; + x.toBits(UInt8.NUM_BITS); + return x; + } } From 321529b534b3bbcd7ceff4fee495cc9ee1a91117 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:29:49 -0700 Subject: [PATCH 0060/1215] refactor(int.ts): add isConstant() method to UInt8 class for checking if the value is constant --- src/lib/int.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 1eb5e7b16b..552849a458 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1011,6 +1011,10 @@ class UInt8 extends Struct({ return new UInt64(this.value); } + isConstant() { + return this.value.isConstant(); + } + static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } From 509c5f9e0f0efe81c5422e078dea70f294eb417e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:31:08 -0700 Subject: [PATCH 0061/1215] refactor(int.ts): add toConstant() method to UInt8 class --- src/lib/int.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 552849a458..dbaf81ac19 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1015,6 +1015,10 @@ class UInt8 extends Struct({ return this.value.isConstant(); } + toConstant() { + return this.value.toConstant(); + } + static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } From 325c49cc48333b6be6dbd496a8b86771890f71b3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:34:06 -0700 Subject: [PATCH 0062/1215] refactor(int.ts): add UInt8 to constructor --- src/lib/int.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index dbaf81ac19..3963541166 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -966,7 +966,9 @@ class UInt8 extends Struct({ }) { static NUM_BITS = 8; - constructor(x: number | bigint | Field) { + constructor(x: number | bigint | Field | UInt8) { + if (x instanceof UInt8) return x; + super({ value: Field(x) }); this.value.toBits(UInt8.NUM_BITS); // Make sure that the Field element that is exactly a byte } @@ -1015,10 +1017,6 @@ class UInt8 extends Struct({ return this.value.isConstant(); } - toConstant() { - return this.value.toConstant(); - } - static fromHex(xs: string): UInt8[] { return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); } From d996d25e798d640d6405c5e76182a73d6f42ab80 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:37:09 -0700 Subject: [PATCH 0063/1215] fix(int.ts): add support for string type in the constructor of UInt8 class to allow initializing with string values --- src/lib/int.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 3963541166..70e2464d9f 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -966,7 +966,7 @@ class UInt8 extends Struct({ }) { static NUM_BITS = 8; - constructor(x: number | bigint | Field | UInt8) { + constructor(x: number | bigint | string | Field | UInt8) { if (x instanceof UInt8) return x; super({ value: Field(x) }); From 738719c19d5ae3c3008e6704136ca3ee1ccd8f6e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 11:37:58 -0700 Subject: [PATCH 0064/1215] test(keccack.unit-test.ts): start unit tests for the constructor of UInt8 class --- src/lib/keccack.unit-test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/lib/keccack.unit-test.ts diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts new file mode 100644 index 0000000000..f8cbfa28e4 --- /dev/null +++ b/src/lib/keccack.unit-test.ts @@ -0,0 +1,20 @@ +import { test, Random } from './testing/property.js'; +import { UInt8 } from './int.js'; + +// Test constructor +test(Random.uint8, Random.json.uint8, (x, y, assert) => { + let z = new UInt8(x); + assert(z instanceof UInt8); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + assert(z.isConstant()); + + assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value)) instanceof UInt8 && z.toBigInt() === x); + + z = new UInt8(y); + assert(z instanceof UInt8); + assert(z.toString() === y); + assert(z.toJSON() === y); +}); From b7bcb035f5d139e7d9682d55bf45fc050aad8fe4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 13:05:14 -0700 Subject: [PATCH 0065/1215] test(keccack.unit-test.ts): add unit test for handling numbers up to 2^8 in the UInt8 class --- src/lib/keccack.unit-test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts index f8cbfa28e4..046d0b810b 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccack.unit-test.ts @@ -18,3 +18,8 @@ test(Random.uint8, Random.json.uint8, (x, y, assert) => { assert(z.toString() === y); assert(z.toJSON() === y); }); + +// handles all numbers up to 2^8 +test(Random.nat(255), (n, assert) => { + assert(new UInt8(n).toString() === String(n)); +}); From 64135dbbd0afc06299d0589baf6a202bfa711c45 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 13:09:45 -0700 Subject: [PATCH 0066/1215] test(keccack.unit-test.ts): add unit tests for negative numbers and numbers greater than or equal to 2^8 to ensure proper handling and error throwing --- src/lib/keccack.unit-test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts index 046d0b810b..acc6865c63 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccack.unit-test.ts @@ -23,3 +23,9 @@ test(Random.uint8, Random.json.uint8, (x, y, assert) => { test(Random.nat(255), (n, assert) => { assert(new UInt8(n).toString() === String(n)); }); + +// throws on negative numbers +test.negative(Random.int(-10, -1), (x) => new UInt8(x)); + +// throws on numbers >= 2^8 +test.negative(Random.uint8.invalid, (x) => new UInt8(x)); From 05550d970d01fad74a8ff3d0bfe35bef2352c58e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 14:38:38 -0700 Subject: [PATCH 0067/1215] feat(keccack.unit-test.ts): add tests for digest to hex and hex to digest conversions --- src/lib/keccack.unit-test.ts | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts index acc6865c63..99029c3e88 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccack.unit-test.ts @@ -1,5 +1,8 @@ import { test, Random } from './testing/property.js'; import { UInt8 } from './int.js'; +import { Hash } from './hash.js'; +import { Provable } from './provable.js'; +import { expect } from 'expect'; // Test constructor test(Random.uint8, Random.json.uint8, (x, y, assert) => { @@ -29,3 +32,47 @@ test.negative(Random.int(-10, -1), (x) => new UInt8(x)); // throws on numbers >= 2^8 test.negative(Random.uint8.invalid, (x) => new UInt8(x)); + +// test digest->hex and hex->digest conversions +checkHashConversions(); +console.log('hashing digest conversions matches! 🎉'); + +function checkHashConversions() { + for (let i = 0; i < 2; i++) { + const data = Random.array(Random.uint8, Random.nat(20)) + .create()() + .map((x) => new UInt8(x)); + + Provable.runAndCheck(() => { + let digest = Hash.SHA224.hash(data); + expectDigestToEqualHex(digest); + + digest = Hash.SHA256.hash(data); + expectDigestToEqualHex(digest); + + digest = Hash.SHA384.hash(data); + expectDigestToEqualHex(digest); + + digest = Hash.SHA512.hash(data); + expectDigestToEqualHex(digest); + + digest = Hash.Keccack256.hash(data); + expectDigestToEqualHex(digest); + }); + } +} + +function expectDigestToEqualHex(digest: UInt8[]) { + Provable.asProver(() => { + const hex = UInt8.toHex(digest); + expect(equals(digest, UInt8.fromHex(hex))).toBe(true); + }); +} + +function equals(a: UInt8[], b: UInt8[]): boolean { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) + if (a[i].value.toConstant() === b[i].value.toConstant()) return false; + + return true; +} From de479249ad72e71808ab762b9d1d2087d828e04b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 14:41:13 -0700 Subject: [PATCH 0068/1215] fix(int.ts): add type checking for UInt32 and UInt64 --- src/lib/int.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 70e2464d9f..2cf422a16a 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1006,11 +1006,15 @@ class UInt8 extends Struct({ } toUInt32(): UInt32 { - return new UInt32(this.value); + let uint32 = new UInt32(this.value); + UInt32.check(uint32); + return uint32; } toUInt64(): UInt64 { - return new UInt64(this.value); + let uint64 = new UInt64(this.value); + UInt64.check(uint64); + return uint64; } isConstant() { From 6c2b45d005e7cb7c4b13224d3be7bba820cb5880 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 14:59:59 -0700 Subject: [PATCH 0069/1215] feat(int.ts): add arithmetic operations (add, sub, mul, div, mod) to UInt8 class and base compare --- src/lib/int.ts | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 2cf422a16a..bfbce1fb88 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -981,6 +981,108 @@ class UInt8 extends Struct({ return new UInt8(1); } + add(y: UInt8 | number) { + if (isUInt8(y)) { + return new UInt8(this.value.add(y.value)); + } + let y_ = new UInt8(y); + return new UInt8(this.value.add(y_.value)); + } + + sub(y: UInt8 | number) { + if (isUInt8(y)) { + return new UInt8(this.value.sub(y.value)); + } + let y_ = new UInt8(y); + return new UInt8(this.value.sub(y_.value)); + } + + mul(y: UInt8 | number) { + if (isUInt8(y)) { + return new UInt8(this.value.mul(y.value)); + } + let y_ = new UInt8(y); + return new UInt8(this.value.mul(y_.value)); + } + + div(y: UInt8 | number) { + if (isUInt8(y)) { + return this.divMod(y).quotient; + } + let y_ = new UInt8(y); + return this.divMod(y_).quotient; + } + + mod(y: UInt8 | number) { + if (isUInt8(y)) { + return this.divMod(y).rest; + } + let y_ = new UInt8(y); + return this.divMod(y_).rest; + } + + divMod(y: UInt8 | number) { + let x = this.value; + let y_ = new UInt8(y).value; + + if (this.value.isConstant() && y_.isConstant()) { + let xn = x.toBigInt(); + let yn = y_.toBigInt(); + let q = xn / yn; + let r = xn - q * yn; + return { + quotient: new UInt8(Field(q)), + rest: new UInt8(Field(r)), + }; + } + + y_ = y_.seal(); + + let q = Provable.witness( + Field, + () => new Field(x.toBigInt() / y_.toBigInt()) + ); + + q.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(q); + + // TODO: Could be a bit more efficient + let r = x.sub(q.mul(y_)).seal(); + r.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(r); + + let r_ = new UInt8(r); + let q_ = new UInt8(q); + + r_.assertLessThan(new UInt8(y_)); + + return { quotient: q_, rest: r_ }; + } + + lessThanOrEqual(y: UInt8) { + 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 = xMinusY + .rangeCheckHelper(UInt8.NUM_BITS) + .equals(xMinusY); + let yMinusXFits = yMinusX + .rangeCheckHelper(UInt8.NUM_BITS) + .equals(yMinusX); + xMinusYFits.or(yMinusXFits).assertEquals(true); + // x <= y if y - x fits in 64 bits + return yMinusXFits; + } + } + + lessThan(y: UInt8) { + return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); + } + + assertLessThan(y: UInt8, message?: string) { + this.lessThan(y).assertEquals(true, message); + } + toString() { return this.value.toString(); } @@ -1050,3 +1152,7 @@ class UInt8 extends Struct({ return x; } } + +function isUInt8(x: unknown): x is UInt8 { + return x instanceof UInt8; +} From fc7597046e14d7407d2a0e0990f9b409518b86a5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 28 Jun 2023 15:07:06 -0700 Subject: [PATCH 0070/1215] feat(int.ts): add assertion methods for comparing UInt8 values --- src/lib/int.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index bfbce1fb88..146b94000f 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1083,6 +1083,41 @@ class UInt8 extends Struct({ this.lessThan(y).assertEquals(true, message); } + assertLessThanOrEqual(y: UInt8, 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(`UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}`); + } + return; + } + let yMinusX = y.value.sub(this.value).seal(); + yMinusX.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(yMinusX, message); + } + + greaterThan(y: UInt8) { + return y.lessThan(this); + } + + greaterThanOrEqual(y: UInt8) { + return this.lessThan(y).not(); + } + + assertGreaterThan(y: UInt8, message?: string) { + y.assertLessThan(this, message); + } + + assertGreaterThanOrEqual(y: UInt8, message?: string) { + y.assertLessThanOrEqual(this, message); + } + + assertEquals(y: number | bigint | UInt8, message?: string) { + let y_ = new UInt8(y); + this.toField().assertEquals(y_.toField(), message); + } + toString() { return this.value.toString(); } From addf84cd7b1f03f299c5246eb33c442c02499af3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 15:59:04 +0200 Subject: [PATCH 0071/1215] working bare bones ec with add --- src/bindings | 2 +- src/lib/foreign-curve.ts | 34 +++++++++++++++++++++++++++--- src/lib/foreign-curve.unit-test.ts | 21 +++++++++++++++--- src/snarky.d.ts | 25 ++++++++++++---------- 4 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/bindings b/src/bindings index ecca66043d..947bfe058d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ecca66043d25f346b17cab6993af5e5ed843663d +Subproject commit 947bfe058d84d4c61443143cd647cb99a24ea183 diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 05bbff8f86..8b3b0348a0 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -11,7 +11,12 @@ import { MlBigint } from './ml/base.js'; export { createForeignCurve }; // internal API -export { ForeignCurveVar, ForeignCurveConst, MlCurveParams }; +export { + ForeignCurveVar, + ForeignCurveConst, + MlCurveParams, + MlCurveParamsWithIa, +}; type MlAffine = [_: 0, x: F, y: F]; type ForeignCurveVar = MlAffine; @@ -21,7 +26,16 @@ type AffineBigint = { x: bigint; y: bigint }; type Affine = { x: ForeignField; y: ForeignField }; function createForeignCurve(curve: CurveParams) { - const curveMl = MlCurveParams(curve); + const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); + let curveMlVar: unknown | undefined; + function getParams(name: string): unknown { + if (curveMlVar === undefined) { + throw Error( + `ForeignCurve.${name}(): You must call ForeignCurve.initialize() once per provable method to use ForeignCurve.` + ); + } + return curveMlVar; + } class BaseField extends createForeignField(curve.modulus) {} class ScalarField extends createForeignField(curve.order) {} @@ -51,8 +65,13 @@ function createForeignCurve(curve: CurveParams) { this.y = BaseField.from(y); } + static initialize() { + curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); + } + add(h: ForeignCurve) { - let p = Snarky.foreignField.curve.add(toMl(this), toMl(h), curveMl); + let curve = getParams('add'); + let p = Snarky.foreignCurve.add(toMl(this), toMl(h), curve); return new ForeignCurve(p); } @@ -104,6 +123,15 @@ type MlCurveParams = [ b: MlBigint, gen: MlBigintPoint ]; +type MlCurveParamsWithIa = [ + _: 0, + modulus: MlBigint, + order: MlBigint, + a: MlBigint, + b: MlBigint, + gen: MlBigintPoint, + ia: [_: 0, acc: MlBigintPoint, neg_acc: MlBigintPoint] +]; function MlCurveParams(params: CurveParams): MlCurveParams { let { modulus, order, a, b, gen } = params; diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index b78ebf688e..a98f6a30b6 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -16,6 +16,21 @@ let gPlusOne = VestaBigint.toAffine( VestaBigint.add(VestaBigint.fromAffine(g), VestaBigint.one) ); -// Provable.runAndCheck(() => { -// let g0 = Provable.witness(Vesta, () => new Vesta(g)); -// }); +function main() { + Vesta.initialize(); + let g0 = new Vesta(g); + g0.add(new Vesta(VestaBigint.one)); + // let g0 = Provable.witness(Vesta, () => new Vesta(g)); +} + +Provable.runAndCheck(main); +let { gates, rows } = Provable.constraintSystem(main); + +let types: Record = {}; + +for (let gate of gates) { + types[gate.type] ??= 0; + types[gate.type]++; +} + +console.log(types); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index a723f73f4f..f80f5bb174 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -16,7 +16,11 @@ import type { ForeignFieldVar, ForeignFieldConst, } from './lib/foreign-field.js'; -import type { ForeignCurveVar, MlCurveParams } from './lib/foreign-curve.js'; +import type { + ForeignCurveVar, + MlCurveParams, + MlCurveParamsWithIa, +} from './lib/foreign-curve.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; @@ -285,16 +289,15 @@ declare const Snarky: { ): ForeignFieldVar; bigintToMl(x: bigint): MlBigint; - - curve: { - create(params: MlCurveParams): unknown; - // paramsToVars() - add( - g: ForeignCurveVar, - h: ForeignCurveVar, - curveParams: unknown - ): ForeignCurveVar; - }; + }; + foreignCurve: { + create(params: MlCurveParams): MlCurveParamsWithIa; + paramsToVars(params: MlCurveParamsWithIa): unknown; + add( + g: ForeignCurveVar, + h: ForeignCurveVar, + curveParams: unknown + ): ForeignCurveVar; }; }; From 578f444537c3fb05a9d329e5a657b7d6336cba1b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 16:31:13 +0200 Subject: [PATCH 0072/1215] make curve provable --- src/lib/foreign-curve.ts | 18 +++++++++++------- src/lib/foreign-curve.unit-test.ts | 11 +++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 8b3b0348a0..33d348931b 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,4 +1,5 @@ import { Snarky } from '../snarky.js'; +import { Struct } from './circuit_value.js'; import { ForeignField, ForeignFieldConst, @@ -25,6 +26,8 @@ type ForeignCurveConst = MlAffine; type AffineBigint = { x: bigint; y: bigint }; type Affine = { x: ForeignField; y: ForeignField }; +type ForeignFieldClass = ReturnType; + function createForeignCurve(curve: CurveParams) { const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); let curveMlVar: unknown | undefined; @@ -44,10 +47,11 @@ function createForeignCurve(curve: CurveParams) { return [0, x.value, y.value]; } - class ForeignCurve implements Affine { - x: BaseField; - y: BaseField; + // this is necessary to simplify the type of ForeignCurve, to avoid + // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. + const Affine: Struct = Struct({ x: BaseField, y: BaseField }); + class ForeignCurve extends Affine { constructor( g: | { x: BaseField | bigint | number; y: BaseField | bigint | number } @@ -56,19 +60,19 @@ function createForeignCurve(curve: CurveParams) { // ForeignCurveVar if (Array.isArray(g)) { let [, x, y] = g; - this.x = new BaseField(x); - this.y = new BaseField(y); + super({ x: new BaseField(x), y: new BaseField(y) }); return; } let { x, y } = g; - this.x = BaseField.from(x); - this.y = BaseField.from(y); + super({ x: BaseField.from(x), y: BaseField.from(y) }); } static initialize() { curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); } + static generator = new ForeignCurve(curve.gen); + add(h: ForeignCurve) { let curve = getParams('add'); let p = Snarky.foreignCurve.add(toMl(this), toMl(h), curve); diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index a98f6a30b6..1e7d61a99b 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -16,15 +16,18 @@ let gPlusOne = VestaBigint.toAffine( VestaBigint.add(VestaBigint.fromAffine(g), VestaBigint.one) ); +// new Vesta(g).add(Vesta.generator); + function main() { Vesta.initialize(); - let g0 = new Vesta(g); - g0.add(new Vesta(VestaBigint.one)); - // let g0 = Provable.witness(Vesta, () => new Vesta(g)); + let g0 = Provable.witness(Vesta, () => new Vesta(g)); + let one = Provable.witness(Vesta, () => Vesta.generator); + let gPlusOne0 = g0.add(one); + Provable.assertEqual(Vesta, gPlusOne0, new Vesta(gPlusOne)); } Provable.runAndCheck(main); -let { gates, rows } = Provable.constraintSystem(main); +let { gates } = Provable.constraintSystem(main); let types: Record = {}; From 4fb98e6a44439ad05bd8c5b4d39c981a976cb9af Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 16:53:14 +0200 Subject: [PATCH 0073/1215] expose common ec methods --- src/bindings | 2 +- src/snarky.d.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 947bfe058d..8a319036db 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 947bfe058d84d4c61443143cd647cb99a24ea183 +Subproject commit 8a319036db1d55dabb864ee9df680c83f34c022b diff --git a/src/snarky.d.ts b/src/snarky.d.ts index f80f5bb174..e09cf406db 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -298,6 +298,15 @@ declare const Snarky: { h: ForeignCurveVar, curveParams: unknown ): ForeignCurveVar; + double(g: ForeignCurveVar, curveParams: unknown): ForeignCurveVar; + negate(g: ForeignCurveVar, curveParams: unknown): ForeignCurveVar; + assertOnCurve(g: ForeignCurveVar, curveParams: unknown): undefined; + scale( + g: ForeignCurveVar, + scalar: MlArray, + curveParams: unknown + ): ForeignCurveVar; + checkSubgroup(g: ForeignCurveVar, curveParams: unknown): undefined; }; }; From 7a228c3fed269fbe41b7e76fb1c8ef488279a71f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 17:12:14 +0200 Subject: [PATCH 0074/1215] add remaining provable methods --- src/lib/foreign-curve.ts | 36 ++++++++++++++++++++++++++++- src/lib/foreign-curve.unit-test.ts | 37 ++++++++++++++++++------------ 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 33d348931b..a1213bc304 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,4 +1,5 @@ import { Snarky } from '../snarky.js'; +import { Bool } from './bool.js'; import { Struct } from './circuit_value.js'; import { ForeignField, @@ -6,7 +7,7 @@ import { ForeignFieldVar, createForeignField, } from './foreign-field.js'; -import { MlBigint } from './ml/base.js'; +import { MlArray, MlBigint } from './ml/base.js'; // external API export { createForeignCurve }; @@ -79,6 +80,39 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } + double() { + let curve = getParams('double'); + let p = Snarky.foreignCurve.double(toMl(this), curve); + return new ForeignCurve(p); + } + + negate() { + let curve = getParams('negate'); + let p = Snarky.foreignCurve.negate(toMl(this), curve); + return new ForeignCurve(p); + } + + assertOnCurve() { + let curve = getParams('assertOnCurve'); + Snarky.foreignCurve.assertOnCurve(toMl(this), curve); + } + + // TODO wrap this in a `Scalar` type which is a Bool array under the hood? + scale(scalar: Bool[]) { + let curve = getParams('scale'); + let p = Snarky.foreignCurve.scale( + toMl(this), + MlArray.to(scalar.map((s) => s.value)), + curve + ); + return new ForeignCurve(p); + } + + checkSubgroup() { + let curve = getParams('checkSubgroup'); + Snarky.foreignCurve.checkSubgroup(toMl(this), curve); + } + static BaseField = BaseField; static ScalarField = ScalarField; } diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 1e7d61a99b..3dfd43a6b9 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -1,39 +1,46 @@ import { createForeignCurve } from './foreign-curve.js'; import { Fp, Fq } from '../bindings/crypto/finite_field.js'; -import { Vesta as VestaBigint } from '../bindings/crypto/elliptic_curve.js'; +import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; import { Provable } from './provable.js'; +import { Field } from './field.js'; class Vesta extends createForeignCurve({ modulus: Fq.modulus, order: Fp.modulus, a: 0n, - b: VestaBigint.b, - gen: VestaBigint.one, + b: V.b, + gen: V.one, }) {} let g = { x: Fq.negate(1n), y: 2n, infinity: false }; -let gPlusOne = VestaBigint.toAffine( - VestaBigint.add(VestaBigint.fromAffine(g), VestaBigint.one) -); - -// new Vesta(g).add(Vesta.generator); +let h = V.toAffine(V.negate(V.double(V.add(V.fromAffine(g), V.one)))); +let scalar = Field.random().toBigInt(); +let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); function main() { Vesta.initialize(); let g0 = Provable.witness(Vesta, () => new Vesta(g)); let one = Provable.witness(Vesta, () => Vesta.generator); - let gPlusOne0 = g0.add(one); - Provable.assertEqual(Vesta, gPlusOne0, new Vesta(gPlusOne)); + let h0 = g0.add(one).double().negate(); + Provable.assertEqual(Vesta, h0, new Vesta(h)); + + h0.assertOnCurve(); + // TODO causes infinite loop + // h0.checkSubgroup(); + + let scalar0 = Provable.witness(Field, () => new Field(scalar)).toBits(); + // TODO causes infinite loop + // let p0 = h0.scale(scalar0); + // Provable.assertEqual(Vesta, p0, new Vesta(p)); } Provable.runAndCheck(main); let { gates } = Provable.constraintSystem(main); -let types: Record = {}; - +let gateTypes: Record = {}; for (let gate of gates) { - types[gate.type] ??= 0; - types[gate.type]++; + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; } -console.log(types); +console.log(gateTypes); From e2410428a1d911fd7d4031e06aa876142c5d8738 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 20:46:43 +0200 Subject: [PATCH 0075/1215] correct comments --- src/lib/foreign-curve.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 3dfd43a6b9..e0f207ea95 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -25,11 +25,11 @@ function main() { Provable.assertEqual(Vesta, h0, new Vesta(h)); h0.assertOnCurve(); - // TODO causes infinite loop + // TODO super slow // h0.checkSubgroup(); let scalar0 = Provable.witness(Field, () => new Field(scalar)).toBits(); - // TODO causes infinite loop + // TODO super slow // let p0 = h0.scale(scalar0); // Provable.assertEqual(Vesta, p0, new Vesta(p)); } From be58d9a1181112565351a289f1b80de3c7acb948 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 12:06:12 -0700 Subject: [PATCH 0076/1215] feat(int.ts): add more unit tests for UInt8 --- src/lib/int.test.ts | 908 ++++++++++++++++++++++++++++++++++- src/lib/int.ts | 27 +- src/lib/keccack.unit-test.ts | 6 +- src/lib/testing/random.ts | 3 - 4 files changed, 906 insertions(+), 38 deletions(-) diff --git a/src/lib/int.test.ts b/src/lib/int.test.ts index e75c5a679f..53599b7202 100644 --- a/src/lib/int.test.ts +++ b/src/lib/int.test.ts @@ -1,28 +1,15 @@ import { - isReady, Provable, - shutdown, Int64, UInt64, UInt32, + UInt8, Field, Bool, Sign, } from 'snarkyjs'; describe('int', () => { - beforeAll(async () => { - await isReady; - }); - - afterAll(async () => { - // Use a timeout to defer the execution of `shutdown()` until Jest processes all tests. - // `shutdown()` exits the process when it's done cleanup so we want to delay it's execution until Jest is done - setTimeout(async () => { - await shutdown(); - }, 0); - }); - const NUMBERMAX = 2 ** 53 - 1; // JavaScript numbers can only safely store integers in the range -(2^53 − 1) to 2^53 − 1 describe('Int64', () => { @@ -2150,4 +2137,897 @@ describe('int', () => { }); }); }); + + describe('UInt8', () => { + const NUMBERMAX = UInt8.MAXINT().value; + + describe('Inside circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.add(y).assertEquals(new UInt8(Field(2))); + }); + }).not.toThrow(); + }); + + it('100+100=200', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + x.add(y).assertEquals(new UInt8(Field(200))); + }); + }).not.toThrow(); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const n = Field((((1n << 8n) - 2n) / 2n).toString()); + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(n)); + const y = Provable.witness(UInt8, () => new UInt8(n)); + x.add(y).add(1).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on overflow addition', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.add(y); + }); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.sub(y).assertEquals(new UInt8(Field(0))); + }); + }).not.toThrow(); + }); + + it('100-50=50', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(50))); + x.sub(y).assertEquals(new UInt8(Field(50))); + }); + }).not.toThrow(); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(0))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.sub(y); + }); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + x.mul(y).assertEquals(new UInt8(Field(2))); + }); + }).not.toThrow(); + }); + + it('1x0=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(0))); + x.mul(y).assertEquals(new UInt8(Field(0))); + }); + }).not.toThrow(); + }); + + it('12x20=240', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(12))); + const y = Provable.witness(UInt8, () => new UInt8(Field(20))); + x.mul(y).assertEquals(new UInt8(Field(240))); + }); + }).not.toThrow(); + }); + + it('MAXINTx1=MAXINT', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.mul(y).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + x.mul(y); + }); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(2))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.div(y).assertEquals(new UInt8(Field(2))); + }); + }).not.toThrow(); + }); + + it('0/1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(0))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.div(y).assertEquals(new UInt8(Field(0))); + }); + }).not.toThrow(); + }); + + it('20/10=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(20))); + const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + x.div(y).assertEquals(new UInt8(Field(2))); + }); + }).not.toThrow(); + }); + + it('MAXINT/1=MAXINT', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.div(y).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on division by zero', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(0))); + x.div(y); + }); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.mod(y).assertEquals(new UInt8(Field(0))); + }); + }).not.toThrow(); + }); + + it('50%32=18', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(50))); + const y = Provable.witness(UInt8, () => new UInt8(Field(32))); + x.mod(y).assertEquals(new UInt8(Field(18))); + }); + }).not.toThrow(); + }); + + it('MAXINT%7=3', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(7))); + x.mod(y).assertEquals(new UInt8(Field(3))); + }); + }).not.toThrow(); + }); + + it('should throw on mod by zero', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(Field(0))); + x.mod(y).assertEquals(new UInt8(Field(1))); + }); + }).toThrow(); + }); + }); + + describe('assertLt', () => { + it('1<2=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + x.assertLessThan(y); + }); + }).not.toThrow(); + }); + + it('1<1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('2<1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(2))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('10<100=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(10))); + const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + x.assertLessThan(y); + }); + }).not.toThrow(); + }); + + it('100<10=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('MAXINT { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThan(y); + }); + }).toThrow(); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(2))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertLessThanOrEqual(y); + }); + }).toThrow(); + }); + + it('10<=100=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(10))); + const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('100<=10=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + x.assertLessThanOrEqual(y); + }); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + }); + + describe('assertGreaterThan', () => { + it('2>1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(2))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertGreaterThan(y); + }); + }).not.toThrow(); + }); + + it('1>1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('1>2=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('100>10=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + x.assertGreaterThan(y); + }); + }).not.toThrow(); + }); + + it('10>100=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1000))); + const y = Provable.witness(UInt8, () => new UInt8(Field(100000))); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('1>=2=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + x.assertGreaterThanOrEqual(y); + }); + }).toThrow(); + }); + + it('100>=10=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(100))); + const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('10>=100=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(Field(10))); + const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + x.assertGreaterThanOrEqual(y); + }); + }).toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.from(1)); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertEquals(y); + }); + }).not.toThrow(); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.from('1')); + const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + x.assertEquals(y); + }); + }).not.toThrow(); + }); + }); + }); + }); + + describe('Outside of circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(new UInt8(Field(1)).add(1).toString()).toEqual('2'); + }); + + it('50+50=100', () => { + expect(new UInt8(Field(50)).add(50).toString()).toEqual('100'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = Field((((1n << 8n) - 2n) / 2n).toString()); + expect( + new UInt8(value) + .add(new UInt8(value)) + .add(new UInt8(Field(1))) + .toString() + ).toEqual(UInt8.MAXINT().toString()); + }); + + it('should throw on overflow addition', () => { + expect(() => { + UInt8.MAXINT().add(1); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(new UInt8(Field(1)).sub(1).toString()).toEqual('0'); + }); + + it('100-50=50', () => { + expect(new UInt8(Field(100)).sub(50).toString()).toEqual('50'); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + UInt8.from(0).sub(1); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(new UInt8(Field(1)).mul(2).toString()).toEqual('2'); + }); + + it('1x0=0', () => { + expect(new UInt8(Field(1)).mul(0).toString()).toEqual('0'); + }); + + it('12x20=240', () => { + expect(new UInt8(Field(12)).mul(20).toString()).toEqual('240'); + }); + + it('MAXINTx1=MAXINT', () => { + expect(UInt8.MAXINT().mul(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + UInt8.MAXINT().mul(2); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(new UInt8(Field(2)).div(1).toString()).toEqual('2'); + }); + + it('0/1=0', () => { + expect(new UInt32(Field(0)).div(1).toString()).toEqual('0'); + }); + + it('20/10=2', () => { + expect(new UInt8(Field(20)).div(10).toString()).toEqual('2'); + }); + + it('MAXINT/1=MAXINT', () => { + expect(UInt8.MAXINT().div(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on division by zero', () => { + expect(() => { + UInt8.MAXINT().div(0); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(new UInt8(Field(1)).mod(1).toString()).toEqual('0'); + }); + + it('50%32=18', () => { + expect(new UInt8(Field(50)).mod(32).toString()).toEqual('18'); + }); + + it('MAXINT%7=3', () => { + expect(UInt8.MAXINT().mod(7).toString()).toEqual('3'); + }); + + it('should throw on mod by zero', () => { + expect(() => { + UInt8.MAXINT().mod(0); + }).toThrow(); + }); + }); + + describe('lessThan', () => { + it('1<2=true', () => { + expect(new UInt8(Field(1)).lessThan(new UInt8(Field(2)))).toEqual( + Bool(true) + ); + }); + + it('1<1=false', () => { + expect(new UInt8(Field(1)).lessThan(new UInt8(Field(1)))).toEqual( + Bool(false) + ); + }); + + it('2<1=false', () => { + expect(new UInt8(Field(2)).lessThan(new UInt8(Field(1)))).toEqual( + Bool(false) + ); + }); + + it('10<100=true', () => { + expect(new UInt8(Field(10)).lessThan(new UInt8(Field(100)))).toEqual( + Bool(true) + ); + }); + + it('100<10=false', () => { + expect(new UInt8(Field(100)).lessThan(new UInt8(Field(10)))).toEqual( + Bool(false) + ); + }); + + it('MAXINT { + expect(UInt8.MAXINT().lessThan(UInt8.MAXINT())).toEqual(Bool(false)); + }); + }); + + describe('lessThanOrEqual', () => { + it('1<=1=true', () => { + expect( + new UInt8(Field(1)).lessThanOrEqual(new UInt8(Field(1))) + ).toEqual(Bool(true)); + }); + + it('2<=1=false', () => { + expect( + new UInt8(Field(2)).lessThanOrEqual(new UInt8(Field(1))) + ).toEqual(Bool(false)); + }); + + it('10<=100=true', () => { + expect( + new UInt8(Field(10)).lessThanOrEqual(new UInt8(Field(100))) + ).toEqual(Bool(true)); + }); + + it('100<=10=false', () => { + expect( + new UInt8(Field(100)).lessThanOrEqual(new UInt8(Field(10))) + ).toEqual(Bool(false)); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(UInt8.MAXINT().lessThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + new UInt8(Field(1)).assertLessThanOrEqual(new UInt8(Field(1))); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + new UInt8(Field(2)).assertLessThanOrEqual(new UInt8(Field(1))); + }).toThrow(); + }); + + it('10<=100=true', () => { + expect(() => { + new UInt8(Field(10)).assertLessThanOrEqual(new UInt8(Field(100))); + }).not.toThrow(); + }); + + it('100<=10=false', () => { + expect(() => { + new UInt8(Field(100)).assertLessThanOrEqual(new UInt8(Field(10))); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + UInt8.MAXINT().assertLessThanOrEqual(UInt8.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('greaterThan', () => { + it('2>1=true', () => { + expect(new UInt8(Field(2)).greaterThan(new UInt8(Field(1)))).toEqual( + Bool(true) + ); + }); + + it('1>1=false', () => { + expect(new UInt8(Field(1)).greaterThan(new UInt8(Field(1)))).toEqual( + Bool(false) + ); + }); + + it('1>2=false', () => { + expect(new UInt8(Field(1)).greaterThan(new UInt8(Field(2)))).toEqual( + Bool(false) + ); + }); + + it('100>10=true', () => { + expect( + new UInt8(Field(100)).greaterThan(new UInt8(Field(10))) + ).toEqual(Bool(true)); + }); + + it('10>100=false', () => { + expect( + new UInt8(Field(10)).greaterThan(new UInt8(Field(100))) + ).toEqual(Bool(false)); + }); + + it('MAXINT>MAXINT=false', () => { + expect(UInt8.MAXINT().greaterThan(UInt8.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('assertGreaterThan', () => { + it('1>1=false', () => { + expect(() => { + new UInt8(Field(1)).assertGreaterThan(new UInt8(Field(1))); + }).toThrow(); + }); + + it('2>1=true', () => { + expect(() => { + new UInt8(Field(2)).assertGreaterThan(new UInt8(Field(1))); + }).not.toThrow(); + }); + + it('10>100=false', () => { + expect(() => { + new UInt8(Field(10)).assertGreaterThan(new UInt8(Field(100))); + }).toThrow(); + }); + + it('100000>1000=true', () => { + expect(() => { + new UInt8(Field(100)).assertGreaterThan(new UInt8(Field(10))); + }).not.toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + UInt8.MAXINT().assertGreaterThan(UInt8.MAXINT()); + }).toThrow(); + }); + }); + + describe('greaterThanOrEqual', () => { + it('2>=1=true', () => { + expect( + new UInt8(Field(2)).greaterThanOrEqual(new UInt8(Field(1))) + ).toEqual(Bool(true)); + }); + + it('1>=1=true', () => { + expect( + new UInt8(Field(1)).greaterThanOrEqual(new UInt8(Field(1))) + ).toEqual(Bool(true)); + }); + + it('1>=2=false', () => { + expect( + new UInt8(Field(1)).greaterThanOrEqual(new UInt8(Field(2))) + ).toEqual(Bool(false)); + }); + + it('100>=10=true', () => { + expect( + new UInt8(Field(100)).greaterThanOrEqual(new UInt8(Field(10))) + ).toEqual(Bool(true)); + }); + + it('10>=100=false', () => { + expect( + new UInt8(Field(10)).greaterThanOrEqual(new UInt8(Field(100))) + ).toEqual(Bool(false)); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(UInt8.MAXINT().greaterThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1>=1=true', () => { + expect(() => { + new UInt8(Field(1)).assertGreaterThanOrEqual(new UInt8(Field(1))); + }).not.toThrow(); + }); + + it('2>=1=true', () => { + expect(() => { + new UInt8(Field(2)).assertGreaterThanOrEqual(new UInt8(Field(1))); + }).not.toThrow(); + }); + + it('10>=100=false', () => { + expect(() => { + new UInt8(Field(10)).assertGreaterThanOrEqual( + new UInt8(Field(100)) + ); + }).toThrow(); + }); + + it('100>=10=true', () => { + expect(() => { + new UInt8(Field(100)).assertGreaterThanOrEqual( + new UInt8(Field(10)) + ); + }).not.toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + UInt32.MAXINT().assertGreaterThanOrEqual(UInt32.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('toString()', () => { + it('should be the same as Field(0)', async () => { + const x = new UInt8(Field(0)); + const y = Field(0); + expect(x.toString()).toEqual(y.toString()); + }); + it('should be the same as 2^8-1', async () => { + const x = new UInt8(Field(String(NUMBERMAX))); + const y = Field(String(NUMBERMAX)); + expect(x.toString()).toEqual(y.toString()); + }); + }); + + describe('check()', () => { + it('should pass checking a MAXINT', () => { + expect(() => { + UInt8.check(UInt8.MAXINT()); + }).not.toThrow(); + }); + + it('should throw checking over MAXINT', () => { + const x = UInt8.MAXINT(); + expect(() => { + UInt8.check(x.add(1)); + }).toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + const x = UInt8.from(1); + expect(x.value).toEqual(new UInt32(Field(1)).value); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt8.from(NUMBERMAX); + expect(x.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + describe('fromString()', () => { + it('should be the same as Field(1)', () => { + const x = UInt8.from('1'); + expect(x.value).toEqual(new UInt32(Field(1)).value); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt8.from(String(NUMBERMAX)); + expect(x.value).toEqual(Field(String(NUMBERMAX))); + }); + }); + }); + }); + }); }); diff --git a/src/lib/int.ts b/src/lib/int.ts index 146b94000f..c8bd9338b5 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1037,17 +1037,13 @@ class UInt8 extends Struct({ } y_ = y_.seal(); - let q = Provable.witness( Field, () => new Field(x.toBigInt() / y_.toBigInt()) ); - q.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(q); - // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - r.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(r); let r_ = new UInt8(r); let q_ = new UInt8(q); @@ -1061,17 +1057,7 @@ class UInt8 extends Struct({ 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 = xMinusY - .rangeCheckHelper(UInt8.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt8.NUM_BITS) - .equals(yMinusX); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; + return this.value.lessThanOrEqual(y.value); } } @@ -1093,8 +1079,7 @@ class UInt8 extends Struct({ } return; } - let yMinusX = y.value.sub(this.value).seal(); - yMinusX.rangeCheckHelper(UInt8.NUM_BITS).assertEquals(yMinusX, message); + return this.lessThanOrEqual(y).assertEquals(true, message); } greaterThan(y: UInt8) { @@ -1174,10 +1159,16 @@ class UInt8 extends Struct({ return new UInt8(255); } - static from(x: UInt64 | UInt32 | Field | number | string | bigint) { + static from( + x: UInt64 | UInt32 | Field | number | string | bigint | number[] + ) { if (x instanceof UInt64 || x instanceof UInt32 || x instanceof UInt8) x = x.value; + if (Array.isArray(x)) { + return new this(Field.fromBytes(x)); + } + return new this(this.checkConstant(Field(x))); } diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts index 99029c3e88..d80908011a 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccack.unit-test.ts @@ -5,7 +5,7 @@ import { Provable } from './provable.js'; import { expect } from 'expect'; // Test constructor -test(Random.uint8, Random.json.uint8, (x, y, assert) => { +test(Random.uint8, Random.uint8, (x, y, assert) => { let z = new UInt8(x); assert(z instanceof UInt8); assert(z.toBigInt() === x); @@ -18,8 +18,8 @@ test(Random.uint8, Random.json.uint8, (x, y, assert) => { z = new UInt8(y); assert(z instanceof UInt8); - assert(z.toString() === y); - assert(z.toJSON() === y); + assert(z.toString() === y.toString()); + assert(z.toJSON() === y.toString()); }); // handles all numbers up to 2^8 diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 74ffe3e32d..7da60c806a 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -116,7 +116,6 @@ type Generators = { const Generators: Generators = { Field: field, Bool: bool, - UInt8: uint8, UInt32: uint32, UInt64: uint64, Sign: sign, @@ -195,7 +194,6 @@ const invalidUint64Json = toString( // some json versions of those types let json_ = { - uint8: { ...toString(uint8), invalid: invalidUint8Json }, uint32: { ...toString(uint32), invalid: invalidUint32Json }, uint64: { ...toString(uint64), invalid: invalidUint64Json }, publicKey: withInvalidBase58(mapWithInvalid(publicKey, PublicKey.toBase58)), @@ -221,7 +219,6 @@ type JsonGenerators = { const JsonGenerators: JsonGenerators = { Field: json_.field, Bool: boolean, - UInt8: json_.uint8, UInt32: json_.uint32, UInt64: json_.uint64, Sign: withInvalidRandomString(map(sign, Sign.toJSON)), From 88c687fdda42fbc05bb30bddc545bc84b219e0a4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 12:35:47 -0700 Subject: [PATCH 0077/1215] feat(keccack.unit-test.ts): add support for provable testing --- src/lib/keccack.unit-test.ts | 74 ++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccack.unit-test.ts index d80908011a..d8f8289e39 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccack.unit-test.ts @@ -4,6 +4,8 @@ import { Hash } from './hash.js'; import { Provable } from './provable.js'; import { expect } from 'expect'; +let RandomUInt8 = Random.map(Random.uint8, (x) => UInt8.from(x)); + // Test constructor test(Random.uint8, Random.uint8, (x, y, assert) => { let z = new UInt8(x); @@ -34,45 +36,67 @@ test.negative(Random.int(-10, -1), (x) => new UInt8(x)); test.negative(Random.uint8.invalid, (x) => new UInt8(x)); // test digest->hex and hex->digest conversions -checkHashConversions(); +checkHashInCircuit(); +checkHashOutCircuit(); console.log('hashing digest conversions matches! 🎉'); -function checkHashConversions() { - for (let i = 0; i < 2; i++) { - const data = Random.array(Random.uint8, Random.nat(20)) - .create()() - .map((x) => new UInt8(x)); +// check in-circuit +function checkHashInCircuit() { + Provable.runAndCheck(() => { + let d0 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + let d1 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + let d2 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + let d3 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + let d4 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + + let data = [d0, d1, d2, d3, d4]; + checkHashConversions(data, true); + }); +} - Provable.runAndCheck(() => { - let digest = Hash.SHA224.hash(data); - expectDigestToEqualHex(digest); +// check out-of-circuit +function checkHashOutCircuit() { + let r = Random.array(RandomUInt8, Random.nat(20)).create()(); + checkHashConversions(r, false); +} - digest = Hash.SHA256.hash(data); - expectDigestToEqualHex(digest); +function checkHashConversions(data: UInt8[], provable: boolean) { + let digest = Hash.SHA224.hash(data); + expectDigestToEqualHex(digest, provable); - digest = Hash.SHA384.hash(data); - expectDigestToEqualHex(digest); + digest = Hash.SHA256.hash(data); + expectDigestToEqualHex(digest, provable); - digest = Hash.SHA512.hash(data); - expectDigestToEqualHex(digest); + digest = Hash.SHA384.hash(data); + expectDigestToEqualHex(digest, provable); - digest = Hash.Keccack256.hash(data); - expectDigestToEqualHex(digest); - }); - } + digest = Hash.SHA512.hash(data); + expectDigestToEqualHex(digest, provable); + + digest = Hash.Keccack256.hash(data); + expectDigestToEqualHex(digest, provable); } -function expectDigestToEqualHex(digest: UInt8[]) { - Provable.asProver(() => { +function expectDigestToEqualHex(digest: UInt8[], provable: boolean) { + if (provable) { + Provable.asProver(() => { + const hex = UInt8.toHex(digest); + expect(equals(digest, UInt8.fromHex(hex), provable)).toBe(true); + }); + } else { const hex = UInt8.toHex(digest); - expect(equals(digest, UInt8.fromHex(hex))).toBe(true); - }); + expect(equals(digest, UInt8.fromHex(hex), provable)).toBe(true); + } } -function equals(a: UInt8[], b: UInt8[]): boolean { +function equals(a: UInt8[], b: UInt8[], provable: boolean): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) - if (a[i].value.toConstant() === b[i].value.toConstant()) return false; + if (provable) { + a[i].assertEquals(b[i]); + } else { + if (a[i].value.toConstant() === b[i].value.toConstant()) return false; + } return true; } From e432819af8a69022c24b9412a5f942c075ce4cc5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 12:37:53 -0700 Subject: [PATCH 0078/1215] refactor(keccak): rename keccack -> keccak --- src/examples/keccak.ts | 4 ++-- src/lib/hash.ts | 2 +- src/lib/{keccack.unit-test.ts => keccak.unit-test.ts} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/lib/{keccack.unit-test.ts => keccak.unit-test.ts} (98%) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 1fcf7557fa..7996858765 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -47,9 +47,9 @@ Provable.runAndCheck(() => { checkDigestHexConversion(digest); }); -console.log('Running keccack hash test'); +console.log('Running keccak hash test'); Provable.runAndCheck(() => { - let digest = Hash.Keccack256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); + let digest = Hash.Keccak256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); checkDigestHexConversion(digest); }); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 853006b9f3..a5137d1696 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -218,5 +218,5 @@ const Hash = { SHA512: buildSHA(512, true), - Keccack256: buildSHA(256, false), + Keccak256: buildSHA(256, false), }; diff --git a/src/lib/keccack.unit-test.ts b/src/lib/keccak.unit-test.ts similarity index 98% rename from src/lib/keccack.unit-test.ts rename to src/lib/keccak.unit-test.ts index d8f8289e39..d4130ba5a5 100644 --- a/src/lib/keccack.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -73,7 +73,7 @@ function checkHashConversions(data: UInt8[], provable: boolean) { digest = Hash.SHA512.hash(data); expectDigestToEqualHex(digest, provable); - digest = Hash.Keccack256.hash(data); + digest = Hash.Keccak256.hash(data); expectDigestToEqualHex(digest, provable); } From 71b4ee753771df7f66b9d68f23ca0cc7a6544962 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 12:54:55 -0700 Subject: [PATCH 0079/1215] refactor(keccak.unit-test.ts): remove checkHashOutCircuit --- src/lib/keccak.unit-test.ts | 63 ++++++++++--------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index d4130ba5a5..31616a2145 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -37,66 +37,37 @@ test.negative(Random.uint8.invalid, (x) => new UInt8(x)); // test digest->hex and hex->digest conversions checkHashInCircuit(); -checkHashOutCircuit(); console.log('hashing digest conversions matches! 🎉'); // check in-circuit function checkHashInCircuit() { Provable.runAndCheck(() => { - let d0 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); - let d1 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); - let d2 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); - let d3 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); - let d4 = Provable.witness(UInt8, () => new UInt8(RandomUInt8.create()())); + let data = Random.array(RandomUInt8, Random.nat(32)) + .create()() + .map((x) => Provable.witness(UInt8, () => new UInt8(x))); - let data = [d0, d1, d2, d3, d4]; - checkHashConversions(data, true); + checkHashConversions(data); }); } -// check out-of-circuit -function checkHashOutCircuit() { - let r = Random.array(RandomUInt8, Random.nat(20)).create()(); - checkHashConversions(r, false); -} - -function checkHashConversions(data: UInt8[], provable: boolean) { - let digest = Hash.SHA224.hash(data); - expectDigestToEqualHex(digest, provable); - - digest = Hash.SHA256.hash(data); - expectDigestToEqualHex(digest, provable); - - digest = Hash.SHA384.hash(data); - expectDigestToEqualHex(digest, provable); - - digest = Hash.SHA512.hash(data); - expectDigestToEqualHex(digest, provable); - - digest = Hash.Keccak256.hash(data); - expectDigestToEqualHex(digest, provable); +function checkHashConversions(data: UInt8[]) { + Provable.asProver(() => { + expectDigestToEqualHex(Hash.SHA224.hash(data)); + expectDigestToEqualHex(Hash.SHA256.hash(data)); + expectDigestToEqualHex(Hash.SHA384.hash(data)); + expectDigestToEqualHex(Hash.SHA512.hash(data)); + expectDigestToEqualHex(Hash.Keccak256.hash(data)); + }); } -function expectDigestToEqualHex(digest: UInt8[], provable: boolean) { - if (provable) { - Provable.asProver(() => { - const hex = UInt8.toHex(digest); - expect(equals(digest, UInt8.fromHex(hex), provable)).toBe(true); - }); - } else { - const hex = UInt8.toHex(digest); - expect(equals(digest, UInt8.fromHex(hex), provable)).toBe(true); - } +function expectDigestToEqualHex(digest: UInt8[]) { + const hex = UInt8.toHex(digest); + expect(equals(digest, UInt8.fromHex(hex))).toBe(true); } -function equals(a: UInt8[], b: UInt8[], provable: boolean): boolean { +function equals(a: UInt8[], b: UInt8[]): boolean { if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) - if (provable) { - a[i].assertEquals(b[i]); - } else { - if (a[i].value.toConstant() === b[i].value.toConstant()) return false; - } + for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); return true; } From db79cd308e2d38a652eee2efd714c2e43a90e72f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 15:04:21 -0700 Subject: [PATCH 0080/1215] feat(vk_regression): add hashing to vk regression tests --- src/examples/primitive_constraint_system.ts | 47 ++++++++++++++++++++- src/examples/vk_regression.ts | 3 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index f03bc3bbf1..454fb58ff5 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -1,4 +1,4 @@ -import { Field, Group, Poseidon, Provable, Scalar } from 'snarkyjs'; +import { Field, Group, Provable, Scalar, Hash, UInt8 } from 'snarkyjs'; function mock(obj: { [K: string]: (...args: any) => void }, name: string) { let methodKeys = Object.keys(obj); @@ -63,4 +63,49 @@ const GroupMock = { }, }; +const HashMock = { + SHA224() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA224.hash(xs); + }, + + SHA256() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA256.hash(xs); + }, + + SHA384() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA384.hash(xs); + }, + + SHA512() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA512.hash(xs); + }, + + Keccak256() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.Keccak256.hash(xs); + }, + + Poseidon() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(Field, () => Field(x)) + ); + Hash.Poseidon.hash(xs); + }, +}; + export const GroupCS = mock(GroupMock, 'Group Primitive'); +export const HashCS = mock(HashMock, 'SHA Primitive'); diff --git a/src/examples/vk_regression.ts b/src/examples/vk_regression.ts index 6dbcd31373..6d301c9aa4 100644 --- a/src/examples/vk_regression.ts +++ b/src/examples/vk_regression.ts @@ -3,7 +3,7 @@ import { Voting_ } from './zkapps/voting/voting.js'; import { Membership_ } from './zkapps/voting/membership.js'; import { HelloWorld } from './zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from './zkapps/dex/dex.js'; -import { GroupCS } from './primitive_constraint_system.js'; +import { GroupCS, HashCS } from './primitive_constraint_system.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; @@ -37,6 +37,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ TokenContract, createDex().Dex, GroupCS, + HashCS, ]; let filePath = jsonPath ? jsonPath : './src/examples/regression_test.json'; From 36e145f57024b609a56ae4a623db8fb6333e65d9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 15:08:48 -0700 Subject: [PATCH 0081/1215] fix(keccak-test): replace usage of ctor with .from --- src/lib/int.ts | 2 +- src/lib/keccak.unit-test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index c8bd9338b5..382f32ca04 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1160,7 +1160,7 @@ class UInt8 extends Struct({ } static from( - x: UInt64 | UInt32 | Field | number | string | bigint | number[] + x: UInt8 | UInt64 | UInt32 | Field | number | string | bigint | number[] ) { if (x instanceof UInt64 || x instanceof UInt32 || x instanceof UInt8) x = x.value; diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 31616a2145..343d1e60df 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -26,14 +26,14 @@ test(Random.uint8, Random.uint8, (x, y, assert) => { // handles all numbers up to 2^8 test(Random.nat(255), (n, assert) => { - assert(new UInt8(n).toString() === String(n)); + assert(UInt8.from(n).toString() === String(n)); }); // throws on negative numbers -test.negative(Random.int(-10, -1), (x) => new UInt8(x)); +test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); // throws on numbers >= 2^8 -test.negative(Random.uint8.invalid, (x) => new UInt8(x)); +test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); // test digest->hex and hex->digest conversions checkHashInCircuit(); @@ -44,7 +44,7 @@ function checkHashInCircuit() { Provable.runAndCheck(() => { let data = Random.array(RandomUInt8, Random.nat(32)) .create()() - .map((x) => Provable.witness(UInt8, () => new UInt8(x))); + .map((x) => Provable.witness(UInt8, () => UInt8.from(x))); checkHashConversions(data); }); From 4360afeef2b011e347d239370f18f9d450f77e6f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 16:12:50 -0700 Subject: [PATCH 0082/1215] feat(examples): start debugging hashing example --- src/examples/zkapps/hashing/hash.ts | 96 +++++++++++++++++++++++++ src/examples/zkapps/hashing/run.ts | 107 ++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/examples/zkapps/hashing/hash.ts create mode 100644 src/examples/zkapps/hashing/run.ts diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts new file mode 100644 index 0000000000..2e361a347d --- /dev/null +++ b/src/examples/zkapps/hashing/hash.ts @@ -0,0 +1,96 @@ +import { + Hash, + UInt8, + Field, + SmartContract, + state, + State, + method, + PrivateKey, + Permissions, + Struct, +} from 'snarkyjs'; + +export const adminPrivateKey = PrivateKey.random(); +export const adminPublicKey = adminPrivateKey.toPublicKey(); + +let initialCommitment: Field = Field(0); + +// 32 UInts +export class HashInput extends Struct({ + data: [ + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + UInt8, + ], +}) {} + +export class HashStorage extends SmartContract { + @state(Field) commitment = State(); + + init() { + super.init(); + this.account.permissions.set({ + ...Permissions.default(), + editState: Permissions.proofOrSignature(), + }); + this.commitment.set(initialCommitment); + } + + @method SHA224(xs: HashInput) { + const shaHash = Hash.SHA224.hash(xs.data); + const commitment = Hash.hash(shaHash.map((f) => f.toField())); + this.commitment.set(commitment); + } + + @method SHA256(xs: HashInput) { + const shaHash = Hash.SHA256.hash(xs.data); + const commitment = Hash.hash(shaHash.map((f) => f.toField())); + this.commitment.set(commitment); + } + + @method SHA384(xs: HashInput) { + const shaHash = Hash.SHA384.hash(xs.data); + const commitment = Hash.hash(shaHash.map((f) => f.toField())); + this.commitment.set(commitment); + } + + @method SHA512(xs: HashInput) { + const shaHash = Hash.SHA512.hash(xs.data); + const commitment = Hash.hash(shaHash.map((f) => f.toField())); + this.commitment.set(commitment); + } + + @method Keccak256(xs: HashInput) { + const shaHash = Hash.Keccak256.hash(xs.data); + const commitment = Hash.hash(shaHash.map((f) => f.toField())); + this.commitment.set(commitment); + } +} diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts new file mode 100644 index 0000000000..47a376bc46 --- /dev/null +++ b/src/examples/zkapps/hashing/run.ts @@ -0,0 +1,107 @@ +import { HashStorage, HashInput } from './hash.js'; +import { Mina, PrivateKey, AccountUpdate, UInt8 } from 'snarkyjs'; +import { getProfiler } from '../../profiler.js'; + +const HashProfier = getProfiler('Hash'); +HashProfier.start('Hash test flow'); + +let txn; +let proofsEnabled = true; +// setup local ledger +let Local = Mina.LocalBlockchain({ proofsEnabled }); +Mina.setActiveInstance(Local); + +if (proofsEnabled) { + console.log('Proofs enabled'); + HashStorage.compile(); +} + +// test accounts that pays all the fees, and puts additional funds into the zkapp +const feePayer = Local.testAccounts[0]; + +// zkapp account +const zkAppPrivateKey = PrivateKey.random(); +const zkAppAddress = zkAppPrivateKey.toPublicKey(); +const zkAppInstance = new HashStorage(zkAppAddress); + +// 0, 1, 2, 3, ..., 32 +const hashData = new HashInput({ + data: Array.from({ length: 32 }, (_, i) => i).map((x) => UInt8.from(x)), +}); + +console.log('Deploying Hash Example....'); + +txn = await Mina.transaction(feePayer.publicKey, () => { + AccountUpdate.fundNewAccount(feePayer.publicKey); + zkAppInstance.deploy(); +}); +await txn.sign([feePayer.privateKey, zkAppPrivateKey]).send(); + +const initialState = + Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +let currentState; + +console.log('Initial State', initialState); + +console.log(`Updating commitment from ${initialState} using SHA224 ...`); + +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA224(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); + +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA256 ...`); + +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); + +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA384 ...`); + +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA384(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); + +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA512 ...`); + +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA512(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); + +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using Keccak256...`); + +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.Keccak256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); + +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +console.log(`Current state successfully updated to ${currentState}`); + +HashProfier.stop().store(); From 5885af01807d66a67ddc76d78b5e9222ad06fb41 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 16:21:54 -0700 Subject: [PATCH 0083/1215] fix(keccak.ts): fix equals function to correctly compare UInt8 arrays --- src/examples/keccak.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 7996858765..243b64996e 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -2,9 +2,7 @@ import { Field, Provable, Hash, UInt8 } from 'snarkyjs'; function equals(a: UInt8[], b: UInt8[]): boolean { if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) - if (a[i].value.toConstant() === b[i].value.toConstant()) return false; - + for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); return true; } From f86b23cbccc89b09d64e5be837d2e2fd50111bee Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 29 Jun 2023 16:23:00 -0700 Subject: [PATCH 0084/1215] refactor(hash.ts): remove unused code and variables for better code cleanliness and maintainability --- src/examples/zkapps/hashing/hash.ts | 4 ---- src/examples/zkapps/hashing/run.ts | 17 ----------------- src/lib/hash.ts | 4 ++-- src/lib/int.ts | 2 +- 4 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts index 2e361a347d..308cf48bce 100644 --- a/src/examples/zkapps/hashing/hash.ts +++ b/src/examples/zkapps/hashing/hash.ts @@ -6,14 +6,10 @@ import { state, State, method, - PrivateKey, Permissions, Struct, } from 'snarkyjs'; -export const adminPrivateKey = PrivateKey.random(); -export const adminPublicKey = adminPrivateKey.toPublicKey(); - let initialCommitment: Field = Field(0); // 32 UInts diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts index 47a376bc46..b413cd09db 100644 --- a/src/examples/zkapps/hashing/run.ts +++ b/src/examples/zkapps/hashing/run.ts @@ -30,7 +30,6 @@ const hashData = new HashInput({ }); console.log('Deploying Hash Example....'); - txn = await Mina.transaction(feePayer.publicKey, () => { AccountUpdate.fundNewAccount(feePayer.publicKey); zkAppInstance.deploy(); @@ -41,67 +40,51 @@ const initialState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); let currentState; - console.log('Initial State', initialState); console.log(`Updating commitment from ${initialState} using SHA224 ...`); - txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.SHA224(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); - currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); - console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using SHA256 ...`); - txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.SHA256(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); - currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); - console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using SHA384 ...`); - txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.SHA384(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); - currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); - console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using SHA512 ...`); - txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.SHA512(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); - currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); - console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using Keccak256...`); - txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.Keccak256(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); - currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); - console.log(`Current state successfully updated to ${currentState}`); HashProfier.stop().store(); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index a5137d1696..cb911bf339 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -199,8 +199,8 @@ function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { hash(message: UInt8[]): UInt8[] { return Snarky.sha - .create([0, ...message.map((f) => f.value.value)], nist, length) - .map((f) => new UInt8(Field(f))); + .create([0, ...message.map((f) => f.toField().value)], nist, length) + .map((f) => UInt8.from(Field(f))); }, }; } diff --git a/src/lib/int.ts b/src/lib/int.ts index 382f32ca04..c196e6328f 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1156,7 +1156,7 @@ class UInt8 extends Struct({ } static MAXINT() { - return new UInt8(255); + return new UInt8(1 << (this.NUM_BITS - 1)); } static from( From 6f0a8887c38dab3e443c8e6cf71a7ba79ead44ef Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 10:58:49 +0200 Subject: [PATCH 0085/1215] minor tweaks --- src/lib/foreign-curve.ts | 14 ++++---------- src/lib/ml/fields.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index a1213bc304..0feb8edb2a 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -7,7 +7,8 @@ import { ForeignFieldVar, createForeignField, } from './foreign-field.js'; -import { MlArray, MlBigint } from './ml/base.js'; +import { MlBigint } from './ml/base.js'; +import { MlBoolArray } from './ml/fields.js'; // external API export { createForeignCurve }; @@ -27,8 +28,6 @@ type ForeignCurveConst = MlAffine; type AffineBigint = { x: bigint; y: bigint }; type Affine = { x: ForeignField; y: ForeignField }; -type ForeignFieldClass = ReturnType; - function createForeignCurve(curve: CurveParams) { const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); let curveMlVar: unknown | undefined; @@ -102,7 +101,7 @@ function createForeignCurve(curve: CurveParams) { let curve = getParams('scale'); let p = Snarky.foreignCurve.scale( toMl(this), - MlArray.to(scalar.map((s) => s.value)), + MlBoolArray.to(scalar), curve ); return new ForeignCurve(p); @@ -162,12 +161,7 @@ type MlCurveParams = [ gen: MlBigintPoint ]; type MlCurveParamsWithIa = [ - _: 0, - modulus: MlBigint, - order: MlBigint, - a: MlBigint, - b: MlBigint, - gen: MlBigintPoint, + ...params: MlCurveParams, ia: [_: 0, acc: MlBigintPoint, neg_acc: MlBigintPoint] ]; diff --git a/src/lib/ml/fields.ts b/src/lib/ml/fields.ts index 4921e9272a..0c092dcdfc 100644 --- a/src/lib/ml/fields.ts +++ b/src/lib/ml/fields.ts @@ -1,6 +1,7 @@ +import { Bool, BoolVar } from '../bool.js'; import { ConstantField, Field, FieldConst, FieldVar } from '../field.js'; import { MlArray } from './base.js'; -export { MlFieldArray, MlFieldConstArray }; +export { MlFieldArray, MlFieldConstArray, MlBoolArray }; type MlFieldArray = MlArray; const MlFieldArray = { @@ -21,3 +22,13 @@ const MlFieldConstArray = { return arr.map((x) => new Field(x) as ConstantField); }, }; + +type MlBoolArray = MlArray; +const MlBoolArray = { + to(arr: Bool[]): MlArray { + return MlArray.to(arr.map((x) => x.value)); + }, + from([, ...arr]: MlArray) { + return arr.map((x) => new Bool(x)); + }, +}; From 44779a19ba18107a0319689076ab4eba46a03e6c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 15:20:45 +0200 Subject: [PATCH 0086/1215] improve api --- src/lib/foreign-curve.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 0feb8edb2a..42395fab64 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -67,15 +67,29 @@ function createForeignCurve(curve: CurveParams) { super({ x: BaseField.from(x), y: BaseField.from(y) }); } + static from( + g: + | ForeignCurve + | { x: BaseField | bigint | number; y: BaseField | bigint | number } + ) { + if (g instanceof ForeignCurve) return g; + return new ForeignCurve(g); + } + static initialize() { curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); } static generator = new ForeignCurve(curve.gen); - add(h: ForeignCurve) { + add( + h: + | ForeignCurve + | { x: BaseField | bigint | number; y: BaseField | bigint | number } + ) { + let h_ = ForeignCurve.from(h); let curve = getParams('add'); - let p = Snarky.foreignCurve.add(toMl(this), toMl(h), curve); + let p = Snarky.foreignCurve.add(toMl(this), toMl(h_), curve); return new ForeignCurve(p); } From 26affb1f95518f5fe7953582512b703a84c1e5d4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 16:17:20 +0200 Subject: [PATCH 0087/1215] stub out ecdsa factory --- src/index.ts | 2 ++ src/lib/foreign-curve-params.ts | 12 ++++++++ src/lib/foreign-curve.ts | 11 ++++---- src/lib/foreign-ecdsa.ts | 45 ++++++++++++++++++++++++++++++ src/lib/foreign-ecdsa.unit-test.ts | 6 ++++ src/lib/foreign-field.ts | 15 ++++++++++ 6 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/lib/foreign-curve-params.ts create mode 100644 src/lib/foreign-ecdsa.ts create mode 100644 src/lib/foreign-ecdsa.unit-test.ts diff --git a/src/index.ts b/src/index.ts index 5d7c05812e..68a95513cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ export type { ProvablePure } from './snarky.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; export { createForeignField, ForeignField } from './lib/foreign-field.js'; +export { createForeignCurve } from './lib/foreign-curve.js'; +export { createEcdsa } from './lib/foreign-ecdsa.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export type { diff --git a/src/lib/foreign-curve-params.ts b/src/lib/foreign-curve-params.ts new file mode 100644 index 0000000000..eabd97d043 --- /dev/null +++ b/src/lib/foreign-curve-params.ts @@ -0,0 +1,12 @@ +import { Fp, Fq } from '../bindings/crypto/finite_field.js'; +import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; + +export { vestaParams }; + +const vestaParams = { + modulus: Fq.modulus, + order: Fp.modulus, + a: 0n, + b: V.b, + gen: V.one, +}; diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 42395fab64..b7b6ab5d24 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -11,7 +11,7 @@ import { MlBigint } from './ml/base.js'; import { MlBoolArray } from './ml/fields.js'; // external API -export { createForeignCurve }; +export { createForeignCurve, CurveParams }; // internal API export { @@ -19,6 +19,7 @@ export { ForeignCurveConst, MlCurveParams, MlCurveParamsWithIa, + ForeignCurveClass, }; type MlAffine = [_: 0, x: F, y: F]; @@ -28,6 +29,8 @@ type ForeignCurveConst = MlAffine; type AffineBigint = { x: bigint; y: bigint }; type Affine = { x: ForeignField; y: ForeignField }; +type ForeignCurveClass = ReturnType; + function createForeignCurve(curve: CurveParams) { const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); let curveMlVar: unknown | undefined; @@ -51,7 +54,7 @@ function createForeignCurve(curve: CurveParams) { // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. const Affine: Struct = Struct({ x: BaseField, y: BaseField }); - class ForeignCurve extends Affine { + return class ForeignCurve extends Affine { constructor( g: | { x: BaseField | bigint | number; y: BaseField | bigint | number } @@ -128,9 +131,7 @@ function createForeignCurve(curve: CurveParams) { static BaseField = BaseField; static ScalarField = ScalarField; - } - - return ForeignCurve; + }; } /** diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts new file mode 100644 index 0000000000..a10f34d45b --- /dev/null +++ b/src/lib/foreign-ecdsa.ts @@ -0,0 +1,45 @@ +import { Bool } from './bool.js'; +import { Struct } from './circuit_value.js'; +import { + CurveParams, + ForeignCurveClass, + createForeignCurve, +} from './foreign-curve.js'; + +// external API +export { createEcdsa }; + +function createEcdsa(curve: CurveParams | ForeignCurveClass) { + let Curve0: ForeignCurveClass = + 'gen' in curve ? createForeignCurve(curve) : curve; + class Curve extends Curve0 {} + class Scalar extends Curve.ScalarField {} + + type Signature = { r: Scalar; s: Scalar }; + const Signature: Struct = Struct({ r: Scalar, s: Scalar }); + + return class EcdsaSignature extends Signature { + from({ r, s }: { r: Scalar | bigint; s: Scalar | bigint }): EcdsaSignature { + return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); + } + + // TODO + fromHex({ r, s }: { r: string; s: string }): EcdsaSignature { + return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); + } + + // TODO + verify(msgHash: Scalar | bigint, publicKey: Curve): Bool { + let msgHash_ = Scalar.from(msgHash); + return new Bool(false); + } + + static check(sig: { r: Scalar; s: Scalar }) { + // TODO: check scalars != 0 in addition to normal check for valid scalars + // use signature_scalar_check + super.check(sig); + } + + static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); + }; +} diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts new file mode 100644 index 0000000000..dcc62fa083 --- /dev/null +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -0,0 +1,6 @@ +import { createEcdsa } from './foreign-ecdsa.js'; +import { vestaParams } from './foreign-curve-params.js'; + +class VestaSignature extends createEcdsa(vestaParams) {} + +console.log(VestaSignature.toJSON(VestaSignature.dummy)); diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 8d0ce8cfeb..38caa5833b 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -409,6 +409,21 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { // this means a user has to take care of proper constraining themselves if (!unsafe) x.assertValidElement(); } + + /** + * Convert foreign field element to JSON + */ + static toJSON(x: ForeignField) { + return x.toBigInt().toString(); + } + + /** + * Convert foreign field element from JSON + */ + static fromJSON(x: string) { + // TODO be more strict about allowed values + return new ForeignField(x); + } } function toFp(x: bigint | string | number | ForeignField) { From 81b911886f1e4a6200561a9d8b3deed446688034 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 17:06:32 +0200 Subject: [PATCH 0088/1215] expose ecdsa methods --- src/bindings | 2 +- src/lib/foreign-curve-params.ts | 4 +- src/lib/foreign-curve.ts | 59 ++++++++++++++++++------------ src/lib/foreign-curve.unit-test.ts | 11 ++---- src/lib/foreign-ecdsa.ts | 43 +++++++++++++++++----- src/snarky.d.ts | 20 +++++++++- 6 files changed, 93 insertions(+), 46 deletions(-) diff --git a/src/bindings b/src/bindings index 8a319036db..1b111269c4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 8a319036db1d55dabb864ee9df680c83f34c022b +Subproject commit 1b111269c4efbe215197a3b9c2574082183966b7 diff --git a/src/lib/foreign-curve-params.ts b/src/lib/foreign-curve-params.ts index eabd97d043..2a699b0638 100644 --- a/src/lib/foreign-curve-params.ts +++ b/src/lib/foreign-curve-params.ts @@ -1,9 +1,11 @@ import { Fp, Fq } from '../bindings/crypto/finite_field.js'; import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; +import { CurveParams } from './foreign-curve.js'; export { vestaParams }; -const vestaParams = { +const vestaParams: CurveParams = { + name: 'Vesta', modulus: Fq.modulus, order: Fp.modulus, a: 0n, diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index b7b6ab5d24..2dfa23e94a 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -20,6 +20,7 @@ export { MlCurveParams, MlCurveParamsWithIa, ForeignCurveClass, + toMl as affineToMl, }; type MlAffine = [_: 0, x: F, y: F]; @@ -29,27 +30,19 @@ type ForeignCurveConst = MlAffine; type AffineBigint = { x: bigint; y: bigint }; type Affine = { x: ForeignField; y: ForeignField }; +function toMl({ x, y }: Affine): ForeignCurveVar { + return [0, x.value, y.value]; +} + type ForeignCurveClass = ReturnType; function createForeignCurve(curve: CurveParams) { const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); - let curveMlVar: unknown | undefined; - function getParams(name: string): unknown { - if (curveMlVar === undefined) { - throw Error( - `ForeignCurve.${name}(): You must call ForeignCurve.initialize() once per provable method to use ForeignCurve.` - ); - } - return curveMlVar; - } + const curveName = curve.name; class BaseField extends createForeignField(curve.modulus) {} class ScalarField extends createForeignField(curve.order) {} - function toMl({ x, y }: Affine): ForeignCurveVar { - return [0, x.value, y.value]; - } - // this is necessary to simplify the type of ForeignCurve, to avoid // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. const Affine: Struct = Struct({ x: BaseField, y: BaseField }); @@ -60,14 +53,19 @@ function createForeignCurve(curve: CurveParams) { | { x: BaseField | bigint | number; y: BaseField | bigint | number } | ForeignCurveVar ) { + let x_: BaseField; + let y_: BaseField; // ForeignCurveVar if (Array.isArray(g)) { let [, x, y] = g; - super({ x: new BaseField(x), y: new BaseField(y) }); - return; + x_ = new BaseField(x); + y_ = new BaseField(y); + } else { + let { x, y } = g; + x_ = BaseField.from(x); + y_ = BaseField.from(y); } - let { x, y } = g; - super({ x: BaseField.from(x), y: BaseField.from(y) }); + super({ x: x_, y: y_ }); } static from( @@ -79,8 +77,17 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(g); } + static #curveMlVar: unknown | undefined; static initialize() { - curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); + this.#curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); + } + static _getParams(name: string): unknown { + if (this.#curveMlVar === undefined) { + throw Error( + `${name}(): You must call ${curveName}.initialize() once per provable method to use ${curveName}.` + ); + } + return this.#curveMlVar; } static generator = new ForeignCurve(curve.gen); @@ -91,31 +98,31 @@ function createForeignCurve(curve: CurveParams) { | { x: BaseField | bigint | number; y: BaseField | bigint | number } ) { let h_ = ForeignCurve.from(h); - let curve = getParams('add'); + let curve = ForeignCurve._getParams(`${curveName}.add`); let p = Snarky.foreignCurve.add(toMl(this), toMl(h_), curve); return new ForeignCurve(p); } double() { - let curve = getParams('double'); + let curve = ForeignCurve._getParams(`${curveName}.double`); let p = Snarky.foreignCurve.double(toMl(this), curve); return new ForeignCurve(p); } negate() { - let curve = getParams('negate'); + let curve = ForeignCurve._getParams(`${curveName}.negate`); let p = Snarky.foreignCurve.negate(toMl(this), curve); return new ForeignCurve(p); } assertOnCurve() { - let curve = getParams('assertOnCurve'); + let curve = ForeignCurve._getParams(`${curveName}.assertOnCurve`); Snarky.foreignCurve.assertOnCurve(toMl(this), curve); } // TODO wrap this in a `Scalar` type which is a Bool array under the hood? scale(scalar: Bool[]) { - let curve = getParams('scale'); + let curve = ForeignCurve._getParams(`${curveName}.scale`); let p = Snarky.foreignCurve.scale( toMl(this), MlBoolArray.to(scalar), @@ -125,7 +132,7 @@ function createForeignCurve(curve: CurveParams) { } checkSubgroup() { - let curve = getParams('checkSubgroup'); + let curve = ForeignCurve._getParams(`${curveName}.checkSubgroup`); Snarky.foreignCurve.checkSubgroup(toMl(this), curve); } @@ -139,6 +146,10 @@ function createForeignCurve(curve: CurveParams) { * y^2 = x^3 + ax + b */ type CurveParams = { + /** + * Human-friendly name for the curve + */ + name: string; /** * Base field modulus */ diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index e0f207ea95..79bfd12cd7 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -1,16 +1,11 @@ import { createForeignCurve } from './foreign-curve.js'; -import { Fp, Fq } from '../bindings/crypto/finite_field.js'; +import { Fq } from '../bindings/crypto/finite_field.js'; import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; import { Provable } from './provable.js'; import { Field } from './field.js'; +import { vestaParams } from './foreign-curve-params.js'; -class Vesta extends createForeignCurve({ - modulus: Fq.modulus, - order: Fp.modulus, - a: 0n, - b: V.b, - gen: V.one, -}) {} +class Vesta extends createForeignCurve(vestaParams) {} let g = { x: Fq.negate(1n), y: 2n, infinity: false }; let h = V.toAffine(V.negate(V.double(V.add(V.fromAffine(g), V.one)))); diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index a10f34d45b..8e1b6ddcd0 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,21 +1,39 @@ +import { Snarky } from 'src/snarky.js'; import { Bool } from './bool.js'; import { Struct } from './circuit_value.js'; import { CurveParams, ForeignCurveClass, + affineToMl, createForeignCurve, } from './foreign-curve.js'; +import { ForeignField, ForeignFieldVar } from './foreign-field.js'; // external API export { createEcdsa }; -function createEcdsa(curve: CurveParams | ForeignCurveClass) { +// internal API +export { ForeignSignatureVar }; + +type MlSignature = [_: 0, x: F, y: F]; +type ForeignSignatureVar = MlSignature; + +type Signature = { r: ForeignField; s: ForeignField }; + +function signatureToMl({ r, s }: Signature): ForeignSignatureVar { + return [0, r.value, s.value]; +} + +function createEcdsa( + curve: CurveParams | ForeignCurveClass, + signatureName = 'EcdsaSignature' +) { let Curve0: ForeignCurveClass = 'gen' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} class Scalar extends Curve.ScalarField {} + class BaseField extends Curve.BaseField {} - type Signature = { r: Scalar; s: Scalar }; const Signature: Struct = Struct({ r: Scalar, s: Scalar }); return class EcdsaSignature extends Signature { @@ -28,16 +46,21 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); } - // TODO - verify(msgHash: Scalar | bigint, publicKey: Curve): Bool { - let msgHash_ = Scalar.from(msgHash); - return new Bool(false); + verify( + msgHash: Scalar | bigint, + publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } + ): void { + let curve = Curve._getParams(`${signatureName}.verify`); + let signatureMl = signatureToMl(this); + let msgHashMl = Scalar.from(msgHash).value; + let publicKeyMl = affineToMl(Curve.from(publicKey)); + Snarky.ecdsa.verify(signatureMl, msgHashMl, publicKeyMl, curve); } - static check(sig: { r: Scalar; s: Scalar }) { - // TODO: check scalars != 0 in addition to normal check for valid scalars - // use signature_scalar_check - super.check(sig); + static check(signature: { r: Scalar; s: Scalar }) { + let curve = Curve._getParams(`${signatureName}.check`); + let signatureMl = signatureToMl(signature); + Snarky.ecdsa.assertValidSignature(signatureMl, curve); } static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index e09cf406db..4a0c8ba55f 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -21,6 +21,7 @@ import type { MlCurveParams, MlCurveParamsWithIa, } from './lib/foreign-curve.js'; +import type { ForeignSignatureVar } from './lib/foreign-ecdsa.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; @@ -290,6 +291,7 @@ declare const Snarky: { bigintToMl(x: bigint): MlBigint; }; + foreignCurve: { create(params: MlCurveParams): MlCurveParamsWithIa; paramsToVars(params: MlCurveParamsWithIa): unknown; @@ -300,13 +302,27 @@ declare const Snarky: { ): ForeignCurveVar; double(g: ForeignCurveVar, curveParams: unknown): ForeignCurveVar; negate(g: ForeignCurveVar, curveParams: unknown): ForeignCurveVar; - assertOnCurve(g: ForeignCurveVar, curveParams: unknown): undefined; + assertOnCurve(g: ForeignCurveVar, curveParams: unknown): void; scale( g: ForeignCurveVar, scalar: MlArray, curveParams: unknown ): ForeignCurveVar; - checkSubgroup(g: ForeignCurveVar, curveParams: unknown): undefined; + checkSubgroup(g: ForeignCurveVar, curveParams: unknown): void; + }; + + ecdsa: { + verify( + signature: ForeignSignatureVar, + msgHash: ForeignFieldVar, + publicKey: ForeignCurveVar, + curveParams: unknown + ): void; + + assertValidSignature( + signature: ForeignSignatureVar, + curveParams: unknown + ): void; }; }; From 1dae1dce5dfb89f9ae5e3dcd040d8dacd93863d7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 17:21:56 +0200 Subject: [PATCH 0089/1215] from hex --- src/lib/foreign-ecdsa.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 8e1b6ddcd0..850bfb262a 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -34,15 +34,24 @@ function createEcdsa( class Scalar extends Curve.ScalarField {} class BaseField extends Curve.BaseField {} - const Signature: Struct = Struct({ r: Scalar, s: Scalar }); + const Signature: Struct & (new (value: Signature) => Signature) = + Struct({ r: Scalar, s: Scalar }); - return class EcdsaSignature extends Signature { + class EcdsaSignature extends Signature { from({ r, s }: { r: Scalar | bigint; s: Scalar | bigint }): EcdsaSignature { return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); } - // TODO - fromHex({ r, s }: { r: string; s: string }): EcdsaSignature { + fromHex(rawSignature: string): EcdsaSignature { + let prefix = rawSignature.slice(0, 2); + let signature = rawSignature.slice(2, 130); + if (prefix !== '0x' || signature.length < 128) { + throw Error( + `${signatureName}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` + ); + } + let r = BigInt(`0x${signature.slice(0, 64)}`); + let s = BigInt(`0x${signature.slice(64)}`); return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); } @@ -64,5 +73,7 @@ function createEcdsa( } static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); - }; + } + + return EcdsaSignature; } From 005a2aab4249240d914c1ba17c8785bf17c0b7c7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 17:39:18 +0200 Subject: [PATCH 0090/1215] start writing test for eth signature --- src/lib/foreign-curve-params.ts | 16 ++++++++++++++-- src/lib/foreign-curve.ts | 20 +++++++++++--------- src/lib/foreign-ecdsa.ts | 26 +++++++++++++++----------- src/lib/foreign-ecdsa.unit-test.ts | 21 ++++++++++++++++++--- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/lib/foreign-curve-params.ts b/src/lib/foreign-curve-params.ts index 2a699b0638..d2e7234768 100644 --- a/src/lib/foreign-curve-params.ts +++ b/src/lib/foreign-curve-params.ts @@ -1,8 +1,20 @@ import { Fp, Fq } from '../bindings/crypto/finite_field.js'; import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; -import { CurveParams } from './foreign-curve.js'; +import type { CurveParams } from './foreign-curve.js'; -export { vestaParams }; +export { secp256k1Params, vestaParams }; + +const secp256k1Params: CurveParams = { + name: 'secp256k1', + modulus: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn, + order: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n, + a: 0n, + b: 7n, + gen: { + x: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n, + y: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n, + }, +}; const vestaParams: CurveParams = { name: 'Vesta', diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 2dfa23e94a..18fd4ab08f 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -79,15 +79,15 @@ function createForeignCurve(curve: CurveParams) { static #curveMlVar: unknown | undefined; static initialize() { - this.#curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); + ForeignCurve.#curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); } static _getParams(name: string): unknown { - if (this.#curveMlVar === undefined) { + if (ForeignCurve.#curveMlVar === undefined) { throw Error( - `${name}(): You must call ${curveName}.initialize() once per provable method to use ${curveName}.` + `${name}(): You must call ${this.name}.initialize() once per provable method to use ${curveName}.` ); } - return this.#curveMlVar; + return ForeignCurve.#curveMlVar; } static generator = new ForeignCurve(curve.gen); @@ -98,31 +98,33 @@ function createForeignCurve(curve: CurveParams) { | { x: BaseField | bigint | number; y: BaseField | bigint | number } ) { let h_ = ForeignCurve.from(h); - let curve = ForeignCurve._getParams(`${curveName}.add`); + let curve = ForeignCurve._getParams(`${this.constructor.name}.add`); let p = Snarky.foreignCurve.add(toMl(this), toMl(h_), curve); return new ForeignCurve(p); } double() { - let curve = ForeignCurve._getParams(`${curveName}.double`); + let curve = ForeignCurve._getParams(`${this.constructor.name}.double`); let p = Snarky.foreignCurve.double(toMl(this), curve); return new ForeignCurve(p); } negate() { - let curve = ForeignCurve._getParams(`${curveName}.negate`); + let curve = ForeignCurve._getParams(`${this.constructor.name}.negate`); let p = Snarky.foreignCurve.negate(toMl(this), curve); return new ForeignCurve(p); } assertOnCurve() { - let curve = ForeignCurve._getParams(`${curveName}.assertOnCurve`); + let curve = ForeignCurve._getParams( + `${this.constructor.name}.assertOnCurve` + ); Snarky.foreignCurve.assertOnCurve(toMl(this), curve); } // TODO wrap this in a `Scalar` type which is a Bool array under the hood? scale(scalar: Bool[]) { - let curve = ForeignCurve._getParams(`${curveName}.scale`); + let curve = ForeignCurve._getParams(`${this.constructor.name}.scale`); let p = Snarky.foreignCurve.scale( toMl(this), MlBoolArray.to(scalar), diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 850bfb262a..df628714e5 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,5 +1,4 @@ -import { Snarky } from 'src/snarky.js'; -import { Bool } from './bool.js'; +import { Snarky } from '../snarky.js'; import { Struct } from './circuit_value.js'; import { CurveParams, @@ -24,10 +23,7 @@ function signatureToMl({ r, s }: Signature): ForeignSignatureVar { return [0, r.value, s.value]; } -function createEcdsa( - curve: CurveParams | ForeignCurveClass, - signatureName = 'EcdsaSignature' -) { +function createEcdsa(curve: CurveParams | ForeignCurveClass) { let Curve0: ForeignCurveClass = 'gen' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} @@ -38,16 +34,24 @@ function createEcdsa( Struct({ r: Scalar, s: Scalar }); class EcdsaSignature extends Signature { - from({ r, s }: { r: Scalar | bigint; s: Scalar | bigint }): EcdsaSignature { + static Curve = Curve0; + + static from({ + r, + s, + }: { + r: Scalar | bigint; + s: Scalar | bigint; + }): EcdsaSignature { return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); } - fromHex(rawSignature: string): EcdsaSignature { + static fromHex(rawSignature: string): EcdsaSignature { let prefix = rawSignature.slice(0, 2); let signature = rawSignature.slice(2, 130); if (prefix !== '0x' || signature.length < 128) { throw Error( - `${signatureName}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` + `${this.constructor.name}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` ); } let r = BigInt(`0x${signature.slice(0, 64)}`); @@ -59,7 +63,7 @@ function createEcdsa( msgHash: Scalar | bigint, publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } ): void { - let curve = Curve._getParams(`${signatureName}.verify`); + let curve = Curve0._getParams(`${this.constructor.name}.verify`); let signatureMl = signatureToMl(this); let msgHashMl = Scalar.from(msgHash).value; let publicKeyMl = affineToMl(Curve.from(publicKey)); @@ -67,7 +71,7 @@ function createEcdsa( } static check(signature: { r: Scalar; s: Scalar }) { - let curve = Curve._getParams(`${signatureName}.check`); + let curve = Curve0._getParams(`${this.constructor.name}.check`); let signatureMl = signatureToMl(signature); Snarky.ecdsa.assertValidSignature(signatureMl, curve); } diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index dcc62fa083..a97a21a6d4 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -1,6 +1,21 @@ import { createEcdsa } from './foreign-ecdsa.js'; -import { vestaParams } from './foreign-curve-params.js'; +import { secp256k1Params } from './foreign-curve-params.js'; +import { createForeignCurve } from './foreign-curve.js'; -class VestaSignature extends createEcdsa(vestaParams) {} +class Secp256k1 extends createForeignCurve(secp256k1Params) {} -console.log(VestaSignature.toJSON(VestaSignature.dummy)); +class EthSignature extends createEcdsa(Secp256k1) {} + +let publicKey = Secp256k1.from({ + x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, + y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n, +}); + +let signature = EthSignature.fromHex( + '0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b' +); + +let msgHash = + 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn; + +signature.verify(msgHash, publicKey); From b237f9c4c0f0af31d0c1eade31b07d993bde1739 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 17:48:18 +0200 Subject: [PATCH 0091/1215] ecdsa test --- src/lib/foreign-ecdsa.unit-test.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index a97a21a6d4..237f7fa9c9 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -1,6 +1,7 @@ import { createEcdsa } from './foreign-ecdsa.js'; import { secp256k1Params } from './foreign-curve-params.js'; import { createForeignCurve } from './foreign-curve.js'; +import { Provable } from './provable.js'; class Secp256k1 extends createForeignCurve(secp256k1Params) {} @@ -18,4 +19,29 @@ let signature = EthSignature.fromHex( let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn; -signature.verify(msgHash, publicKey); +console.time('ecdsa verify (witness gen / check)'); +Provable.runAndCheck(() => { + Secp256k1.initialize(); + let signature0 = Provable.witness(EthSignature, () => signature); + + signature0.verify(msgHash, publicKey); +}); +console.timeEnd('ecdsa verify (witness gen / check)'); + +console.time('ecdsa verify (build constraint system)'); +let cs = Provable.constraintSystem(() => { + Secp256k1.initialize(); + let signature0 = Provable.witness(EthSignature, () => signature); + + signature0.verify(msgHash, publicKey); +}); +console.timeEnd('ecdsa verify (build constraint system)'); + +let gateTypes: Record = {}; +gateTypes['Total rows'] = cs.rows; +for (let gate of cs.gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; +} + +console.log(gateTypes); From 068fb4de239a907c2c42c7d493f5ba96d7b7f15f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 30 Jun 2023 12:41:55 -0700 Subject: [PATCH 0092/1215] feat(UInt8): refactor ctor to use .from instead --- src/lib/int.ts | 49 ++++++++++++++----------------------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index c196e6328f..f326e2ed76 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -974,56 +974,36 @@ class UInt8 extends Struct({ } static get zero() { - return new UInt8(0); + return UInt8.from(0); } static get one() { - return new UInt8(1); + return UInt8.from(1); } add(y: UInt8 | number) { - if (isUInt8(y)) { - return new UInt8(this.value.add(y.value)); - } - let y_ = new UInt8(y); - return new UInt8(this.value.add(y_.value)); + return UInt8.from(this.value.add(UInt8.from(y).value)); } sub(y: UInt8 | number) { - if (isUInt8(y)) { - return new UInt8(this.value.sub(y.value)); - } - let y_ = new UInt8(y); - return new UInt8(this.value.sub(y_.value)); + return UInt8.from(this.value.sub(UInt8.from(y).value)); } mul(y: UInt8 | number) { - if (isUInt8(y)) { - return new UInt8(this.value.mul(y.value)); - } - let y_ = new UInt8(y); - return new UInt8(this.value.mul(y_.value)); + return UInt8.from(this.value.mul(UInt8.from(y).value)); } div(y: UInt8 | number) { - if (isUInt8(y)) { - return this.divMod(y).quotient; - } - let y_ = new UInt8(y); - return this.divMod(y_).quotient; + return this.divMod(y).quotient; } mod(y: UInt8 | number) { - if (isUInt8(y)) { - return this.divMod(y).rest; - } - let y_ = new UInt8(y); - return this.divMod(y_).rest; + return this.divMod(y).rest; } divMod(y: UInt8 | number) { let x = this.value; - let y_ = new UInt8(y).value; + let y_ = UInt8.from(y).value; if (this.value.isConstant() && y_.isConstant()) { let xn = x.toBigInt(); @@ -1031,8 +1011,8 @@ class UInt8 extends Struct({ let q = xn / yn; let r = xn - q * yn; return { - quotient: new UInt8(Field(q)), - rest: new UInt8(Field(r)), + quotient: UInt8.from(Field(q)), + rest: UInt8.from(Field(r)), }; } @@ -1045,11 +1025,10 @@ class UInt8 extends Struct({ // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - let r_ = new UInt8(r); - let q_ = new UInt8(q); - - r_.assertLessThan(new UInt8(y_)); + let r_ = UInt8.from(r); + let q_ = UInt8.from(q); + r_.assertLessThan(UInt8.from(y_)); return { quotient: q_, rest: r_ }; } @@ -1156,7 +1135,7 @@ class UInt8 extends Struct({ } static MAXINT() { - return new UInt8(1 << (this.NUM_BITS - 1)); + return new UInt8(Field((1n << BigInt(this.NUM_BITS)) - 1n)); } static from( From b59bb9028ad3c6a0a0c938bd64896a381a537469 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 3 Jul 2023 12:17:08 -0700 Subject: [PATCH 0093/1215] feat(uint8): add most doc comments --- src/lib/int.ts | 406 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 375 insertions(+), 31 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index f326e2ed76..260cdab180 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -961,11 +961,21 @@ class Int64 extends CircuitValue implements BalanceChange { } } +/** + * A 8 bit unsigned integer with values ranging from 0 to 255. + */ class UInt8 extends Struct({ value: Field, }) { static NUM_BITS = 8; + /** + * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a {@link UInt8}. + * The max value of a {@link UInt8} is `2^8 - 1 = 255`. + * + * + * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. + */ constructor(x: number | bigint | string | Field | UInt8) { if (x instanceof UInt8) return x; @@ -973,37 +983,131 @@ class UInt8 extends Struct({ this.value.toBits(UInt8.NUM_BITS); // Make sure that the Field element that is exactly a byte } + /** + * Static method to create a {@link UInt8} with value `0`. + */ static get zero() { return UInt8.from(0); } + /** + * Static method to create a {@link UInt8} with value `1`. + */ static get one() { return UInt8.from(1); } - add(y: UInt8 | number) { - return UInt8.from(this.value.add(UInt8.from(y).value)); + /** + * Add a {@link UInt8} value to another {@link UInt8} element. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const sum = x.add(UInt8.from(5)); + * + * sum.assertEquals(UInt8.from(8)); + * ``` + * + * **Warning**: This operation cannot overflow past 255, an error is thrown if the result is greater than 255. + * + * @param value - a {@link UInt8} value to add to the {@link UInt8}. + * + * @return A {@link UInt8} element that is the sum of the two values. + */ + add(value: UInt8 | number) { + return UInt8.from(this.value.add(UInt8.from(value).value)); } - sub(y: UInt8 | number) { - return UInt8.from(this.value.sub(UInt8.from(y).value)); + /** + * Subtract a {@link UInt8} value by another {@link UInt8} element. + * + * @example + * ```ts + * const x = UInt8.from(8); + * const difference = x.sub(UInt8.from(5)); + * + * difference.assertEquals(UInt8.from(3)); + * ``` + * + * @param value - a {@link UInt8} value to subtract from the {@link UInt8}. + * + * @return A {@link UInt8} element that is the difference of the two values. + */ + sub(value: UInt8 | number) { + return UInt8.from(this.value.sub(UInt8.from(value).value)); } - mul(y: UInt8 | number) { - return UInt8.from(this.value.mul(UInt8.from(y).value)); + /** + * Multiply a {@link UInt8} value by another {@link UInt8} element. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const product = x.mul(UInt8.from(5)); + * + * product.assertEquals(UInt8.from(15)); + * ``` + * + * **Warning**: This operation cannot overflow past 255, an error is thrown if the result is greater than 255. + * + * @param value - a {@link UInt8} value to multiply with the {@link UInt8}. + * + * @return A {@link UInt8} element that is the product of the two values. + */ + mul(value: UInt8 | number) { + return UInt8.from(this.value.mul(UInt8.from(value).value)); } - div(y: UInt8 | number) { - return this.divMod(y).quotient; + /** + * Divide a {@link UInt8} value by another {@link UInt8} element. + * + * Proves that the denominator is non-zero, or throws a "Division by zero" error. + * + * @example + * ```ts + * const x = UInt8.from(6); + * const quotient = x.div(UInt8.from(3)); + * + * quotient.assertEquals(UInt8.from(2)); + * ``` + * + * @param value - a {@link UInt8} value to divide with the {@link UInt8}. + * + * @return A {@link UInt8} element that is the division of the two values. + */ + div(value: UInt8 | number) { + return this.divMod(value).quotient; } - mod(y: UInt8 | number) { - return this.divMod(y).rest; + /** + * Get the remainder a {@link UInt8} value of division of another {@link UInt8} element. + * + * @example + * ```ts + * const x = UInt8.from(50); + * const mod = x.mod(UInt8.from(30)); + * + * mod.assertEquals(UInt8.from(18)); + * ``` + * + * @param value - a {@link UInt8} to get the modulus with another {@link UInt8}. + * + * @return A {@link UInt8} element that is the modulus of the two values. + */ + mod(value: UInt8 | number) { + return this.divMod(value).rest; } - divMod(y: UInt8 | number) { + /** + * Get the quotient and remainder of a {@link UInt8} value divided by another {@link UInt8} element. + * + * @param value - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. + * + * @return The quotient and remainder of the two values. + */ + divMod(value: UInt8 | number) { let x = this.value; - let y_ = UInt8.from(y).value; + let y_ = UInt8.from(value).value; if (this.value.isConstant() && y_.isConstant()) { let xn = x.toBigInt(); @@ -1032,6 +1136,23 @@ class UInt8 extends Struct({ return { quotient: q_, rest: r_ }; } + /** + * Check if this {@link UInt8} is less than or equal to another {@link UInt8} value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * UInt8.from(3).lessThanOrEqual(UInt8.from(5)).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * + * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * + * @return A {@link Bool} representing if this {@link UInt8} is less than or equal another {@link UInt8} value. + */ lessThanOrEqual(y: UInt8) { if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); @@ -1040,90 +1161,307 @@ class UInt8 extends Struct({ } } - lessThan(y: UInt8) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); + /** + * Check if this {@link UInt8} is less than another {@link UInt8} value. + * Returns a {@link Bool}, which is a provable type and can be used prove to the validity of this statement. + * + * @example + * ```ts + * UInt8.from(2).lessThan(UInt8.from(3)).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * + * @return A {@link Bool} representing if this {@link UInt8} is less than another {@link UInt8} value. + */ + lessThan(value: UInt8) { + return this.lessThanOrEqual(value).and( + this.value.equals(value.value).not() + ); } - assertLessThan(y: UInt8, message?: string) { - this.lessThan(y).assertEquals(true, message); + /** + * Assert that this {@link UInt8} is less than another {@link UInt8} value. + * Calling this function is equivalent to `UInt8(...).lessThan(...).assertEquals(Bool(true))`. + * See {@link UInt8.lessThan} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThan(value: UInt8, message?: string) { + this.lessThan(value).assertEquals(true, message); } - assertLessThanOrEqual(y: UInt8, message?: string) { - if (this.value.isConstant() && y.value.isConstant()) { + /** + * Assert that this {@link UInt8} is less than or equal to another {@link UInt8} value. + * Calling this function is equivalent to `UInt8(...).lessThanOrEqual(...).assertEquals(Bool(true))`. + * See {@link UInt8.lessThanOrEqual} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThanOrEqual(value: UInt8, message?: string) { + if (this.value.isConstant() && value.value.isConstant()) { let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); + let y0 = value.value.toBigInt(); if (x0 > y0) { if (message !== undefined) throw Error(message); throw Error(`UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}`); } return; } - return this.lessThanOrEqual(y).assertEquals(true, message); + return this.lessThanOrEqual(value).assertEquals(true, message); } - greaterThan(y: UInt8) { - return y.lessThan(this); + /** + * Check if this {@link UInt8} is greater than another {@link UInt8} value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * UInt8.from(5).greaterThan(UInt8.from(3)).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods currently only support Field elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * + * @return A {@link Bool} representing if this {@link UInt8} is greater than another {@link UInt8} value. + */ + greaterThan(value: UInt8) { + return value.lessThan(this); } - greaterThanOrEqual(y: UInt8) { - return this.lessThan(y).not(); + /** + * Check if this {@link UInt8} is greater than or equal another {@link UInt8} value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * UInt8.from(3).greaterThanOrEqual(UInt8.from(3)).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link UInt8} is greater than or equal another {@link UInt8} value. + */ + greaterThanOrEqual(value: UInt8) { + return this.lessThan(value).not(); } - assertGreaterThan(y: UInt8, message?: string) { - y.assertLessThan(this, message); + /** + * Assert that this {@link UInt8} is greater than another {@link UInt8} value. + * Calling this function is equivalent to `UInt8(...).greaterThan(...).assertEquals(Bool(true))`. + * See {@link UInt8.greaterThan} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThan(value: UInt8, message?: string) { + value.assertLessThan(this, message); } - assertGreaterThanOrEqual(y: UInt8, message?: string) { - y.assertLessThanOrEqual(this, message); + /** + * Assert that this {@link UInt8} is greater than or equal to another {@link UInt8} value. + * Calling this function is equivalent to `UInt8(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. + * See {@link UInt8.greaterThanOrEqual} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. + * The method will throw if one of the inputs exceeds 8 bits. + * + * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThanOrEqual(value: UInt8, message?: string) { + value.assertLessThanOrEqual(this, message); } - assertEquals(y: number | bigint | UInt8, message?: string) { - let y_ = new UInt8(y); + /** + * Assert that this {@link UInt8} is equal another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertEquals(value: number | bigint | UInt8, message?: string) { + let y_ = new UInt8(value); this.toField().assertEquals(y_.toField(), message); } + /** + * Serialize the {@link UInt8} to a string, e.g. for printing. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link UInt8}. Use the operation only during debugging. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * console.log(someUInt8 .toString()); + * ``` + * + * @return A string equivalent to the string representation of the {@link UInt8}. + */ toString() { return this.value.toString(); } + /** + * Serialize the {@link UInt8} to a bigint, e.g. for printing. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the bigint representation of the {@link UInt8}. Use the operation only during debugging. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * console.log(someUInt8.toBigInt()); + * ``` + * + * @return A bigint equivalent to the bigint representation of the {@link UInt8}. + */ toBigInt() { return this.value.toBigInt(); } + /** + * Serialize the {@link UInt8} to a {@link Field}. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * console.log(someUInt8.toField()); + * ``` + * + * @return A {@link Field} equivalent to the bigint representation of the {@link UInt8}. + */ toField() { return this.value; } + /** + * This function is the implementation of {@link Provable.check} in {@link UInt8} type. + * + * This function is called by {@link Provable.check} to check if the {@link UInt8} is valid. + * To check if a {@link UInt8} is valid, we need to check if the value fits in {@link UInt8.NUM_BITS} bits. + * + * @param value - the {@link UInt8} element to check. + */ check() { this.value.toBits(UInt8.NUM_BITS); } + /** + * Implementation of {@link Provable.fromFields} for the {@link UInt8} type. + * + * **Warning**: This function is designed for internal use. It is not intended to be used by a zkApp developer. + * + * @param fields - an array of {@link UInt8} serialized from {@link Field} elements. + * + * @return An array of {@link UInt8} elements of the given array. + */ fromFields(xs: Field[]): UInt8[] { return xs.map((x) => new UInt8(x)); } + /** + * Serialize the {@link UInt8} to a JSON string, e.g. for printing. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link UInt8}. Use the operation only during debugging. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * console.log(someUInt8 .toJSON()); + * ``` + * + * @return A string equivalent to the JSON representation of the {@link Field}. + */ toJSON(): string { return this.value.toString(); } + /** + * Turns a {@link UInt8} into a {@link UInt32}. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * const someUInt32 = someUInt8.toUInt32(); + * ``` + * + * @return A {@link UInt32} equivalent to the {@link UInt8}. + */ toUInt32(): UInt32 { let uint32 = new UInt32(this.value); UInt32.check(uint32); return uint32; } + /** + * Turns a {@link UInt8} into a {@link UInt64}. + * + * @example + * ```ts + * const someUInt8 = UInt8.from(42); + * const someUInt64 = someUInt8.toUInt64(); + * ``` + * + * @return A {@link UInt64} equivalent to the {@link UInt8}. + * */ toUInt64(): UInt64 { let uint64 = new UInt64(this.value); UInt64.check(uint64); return uint64; } + /** + * Check whether this {@link UInt8} element is a hard-coded constant in the constraint system. + * If a {@link UInt8} is constructed outside a zkApp method, it is a constant. + * + * @example + * ```ts + * console.log(UInt8.from(42).isConstant()); // true + * ``` + * + * @example + * ```ts + * \@method myMethod(x: UInt8) { + * console.log(x.isConstant()); // false + * } + * ``` + * + * @return A `boolean` showing if this {@link UInt8} is a constant or not. + */ isConstant() { return this.value.isConstant(); } static fromHex(xs: string): UInt8[] { - return Snarky.sha.fieldBytesFromHex(xs).map((x) => new UInt8(Field(x))); + return Snarky.sha.fieldBytesFromHex(xs).map((x) => UInt8.from(Field(x))); } static toHex(xs: UInt8[]): string { @@ -1134,10 +1472,16 @@ class UInt8 extends Struct({ .join(''); } + /** + * Creates a {@link UInt8} with a value of 255. + */ static MAXINT() { return new UInt8(Field((1n << BigInt(this.NUM_BITS)) - 1n)); } + /** + * Creates a new {@link UInt8}. + */ static from( x: UInt8 | UInt64 | UInt32 | Field | number | string | bigint | number[] ) { From 2fdafc65276033c018063c1a9e1bfab5f6e72891 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 3 Jul 2023 12:24:17 -0700 Subject: [PATCH 0094/1215] chore(uint8: add TODO comments for more efficient range checking --- src/lib/int.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 260cdab180..0ae548bc7d 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1125,10 +1125,13 @@ class UInt8 extends Struct({ Field, () => new Field(x.toBigInt() / y_.toBigInt()) ); + // TODO: Need to range check `q` // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); + // TODO: Need to range check `r` + let r_ = UInt8.from(r); let q_ = UInt8.from(q); @@ -1157,6 +1160,7 @@ class UInt8 extends Struct({ if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); } else { + // TODO: Need more efficient range checking return this.value.lessThanOrEqual(y.value); } } @@ -1223,6 +1227,7 @@ class UInt8 extends Struct({ } return; } + // TODO: Need more efficient range checking return this.lessThanOrEqual(value).assertEquals(true, message); } From 3e24ff7d98a4650cb2982dd382d07b149a817b05 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 3 Jul 2023 14:39:29 -0700 Subject: [PATCH 0095/1215] refactor(hash.ts): move fromHex and toHex from UInt8 to Hash namespace --- src/examples/keccak.ts | 4 ++-- src/lib/hash.ts | 12 ++++++++++++ src/lib/int.ts | 12 ------------ src/lib/keccak.unit-test.ts | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 243b64996e..7f3a26bc58 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -9,8 +9,8 @@ function equals(a: UInt8[], b: UInt8[]): boolean { function checkDigestHexConversion(digest: UInt8[]) { console.log('Checking hex->digest, digest->hex matches'); Provable.asProver(() => { - const hex = UInt8.toHex(digest); - const expected = UInt8.fromHex(hex); + const hex = Hash.toHex(digest); + const expected = Hash.fromHex(hex); if (equals(digest, expected)) { console.log('✅ Digest matches'); } else { diff --git a/src/lib/hash.ts b/src/lib/hash.ts index cb911bf339..e4018964be 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -219,4 +219,16 @@ const Hash = { SHA512: buildSHA(512, true), Keccak256: buildSHA(256, false), + + fromHex(xs: string): UInt8[] { + return Snarky.sha.fieldBytesFromHex(xs).map((x) => UInt8.from(Field(x))); + }, + + toHex(xs: UInt8[]): string { + return xs + .map((x) => x.value) + .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) + .slice(1) + .join(''); + }, }; diff --git a/src/lib/int.ts b/src/lib/int.ts index 0ae548bc7d..e8cd7ae47b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1465,18 +1465,6 @@ class UInt8 extends Struct({ return this.value.isConstant(); } - static fromHex(xs: string): UInt8[] { - return Snarky.sha.fieldBytesFromHex(xs).map((x) => UInt8.from(Field(x))); - } - - static toHex(xs: UInt8[]): string { - return xs - .map((x) => x.value) - .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) - .slice(1) - .join(''); - } - /** * Creates a {@link UInt8} with a value of 255. */ diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 343d1e60df..b36ba5c9be 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -61,8 +61,8 @@ function checkHashConversions(data: UInt8[]) { } function expectDigestToEqualHex(digest: UInt8[]) { - const hex = UInt8.toHex(digest); - expect(equals(digest, UInt8.fromHex(hex))).toBe(true); + const hex = Hash.toHex(digest); + expect(equals(digest, Hash.fromHex(hex))).toBe(true); } function equals(a: UInt8[], b: UInt8[]): boolean { From 003c1ca0af9deed420e1772709cb17e9c8989efc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 3 Jul 2023 16:50:43 -0700 Subject: [PATCH 0096/1215] feat(keccak): add ocaml unit tests --- src/lib/hash.ts | 9 +- src/lib/keccak.unit-test.ts | 208 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 3 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index e4018964be..c4a08be54c 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -200,7 +200,8 @@ function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { hash(message: UInt8[]): UInt8[] { return Snarky.sha .create([0, ...message.map((f) => f.toField().value)], nist, length) - .map((f) => UInt8.from(Field(f))); + .map((f) => UInt8.from(Field(f))) + .slice(1); }, }; } @@ -221,14 +222,16 @@ const Hash = { Keccak256: buildSHA(256, false), fromHex(xs: string): UInt8[] { - return Snarky.sha.fieldBytesFromHex(xs).map((x) => UInt8.from(Field(x))); + return Snarky.sha + .fieldBytesFromHex(xs) + .map((x) => UInt8.from(Field(x))) + .slice(1); }, toHex(xs: UInt8[]): string { return xs .map((x) => x.value) .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) - .slice(1) .join(''); }, }; diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index b36ba5c9be..e86361d3e5 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -3,6 +3,7 @@ import { UInt8 } from './int.js'; import { Hash } from './hash.js'; import { Provable } from './provable.js'; import { expect } from 'expect'; +import assert from 'assert'; let RandomUInt8 = Random.map(Random.uint8, (x) => UInt8.from(x)); @@ -35,6 +36,9 @@ test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); // throws on numbers >= 2^8 test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); +runHashFunctionTests(); +console.log('OCaml tests pass! 🎉'); + // test digest->hex and hex->digest conversions checkHashInCircuit(); console.log('hashing digest conversions matches! 🎉'); @@ -66,8 +70,212 @@ function expectDigestToEqualHex(digest: UInt8[]) { } function equals(a: UInt8[], b: UInt8[]): boolean { + // Provable.log('DEBUG', 'a', a); + // Provable.log('DEBUG', 'b', b); + if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); return true; } + +/** + * Based off the following unit tests from the OCaml implementation: + * https://github.com/MinaProtocol/mina/blob/69d6ea4a3b7ca1690cf8f41d4598cb7484359e1d/src/lib/crypto/kimchi_backend/gadgets/keccak.ml#L646 + */ +function runHashFunctionTests() { + // Positive Tests + testExpected({ + nist: false, + length: 256, + message: '30', + expected: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', + }); + + testExpected({ + nist: true, + length: 512, + message: '30', + expected: + '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', + }); + + testExpected({ + nist: false, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }); + + testExpected({ + nist: false, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', + }); + + testExpected({ + nist: true, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', + }); + + testExpected({ + nist: false, + length: 256, + message: + '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', + }); + + testExpected({ + nist: false, + length: 256, + message: + 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', + }); + + testExpected({ + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }); + + testExpected({ + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }); + + testExpected({ + nist: false, + length: 256, + message: 'a2c0', + expected: + '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', + }); + + testExpected({ + nist: false, + length: 256, + message: '0a2c', + expected: + '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', + }); + + testExpected({ + nist: false, + length: 256, + message: '00', + expected: + 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', + }); + + // Negative tests + try { + testExpected({ + nist: false, + length: 256, + message: 'a2c', + expected: + '07f02d241eeba9c909a1be75e08d9e8ac3e61d9e24fa452a6785083e1527c467', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: '0', + expected: + 'f39f4526920bb4c096e5722d64161ea0eb6dbd0b4ff0d812f31d56fb96142084', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: '30', + expected: + 'f9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e4', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }); + assert(false, 'Expected to throw'); + } catch (e) {} +} + +function testExpected({ + message, + expected, + nist = false, + length = 256, +}: { + message: string; + expected: string; + nist: boolean; + length: number; +}) { + Provable.runAndCheck(() => { + assert(message.length % 2 === 0); + + let fields = Hash.fromHex(message); + let expectedHash = Hash.fromHex(expected); + + Provable.asProver(() => { + if (nist) { + let hashed; + switch (length) { + case 224: + hashed = Hash.SHA224.hash(fields); + break; + case 256: + hashed = Hash.SHA256.hash(fields); + break; + case 384: + hashed = Hash.SHA384.hash(fields); + break; + case 512: + hashed = Hash.SHA512.hash(fields); + break; + default: + assert(false); + } + equals(hashed!, expectedHash); + } else { + let hashed = Hash.Keccak256.hash(fields); + equals(hashed, expectedHash); + } + }); + }); +} From 36ad8953057e99f4b5f2ad1d4fd8aa439d7eda8c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 5 Jul 2023 13:00:03 +0200 Subject: [PATCH 0097/1215] minor --- src/lib/foreign-curve.ts | 2 +- src/lib/foreign-ecdsa.ts | 2 +- src/lib/foreign-ecdsa.unit-test.ts | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 18fd4ab08f..68f392dc64 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -139,7 +139,7 @@ function createForeignCurve(curve: CurveParams) { } static BaseField = BaseField; - static ScalarField = ScalarField; + static Scalar = ScalarField; }; } diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index df628714e5..aafafe2ed5 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -27,7 +27,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let Curve0: ForeignCurveClass = 'gen' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} - class Scalar extends Curve.ScalarField {} + class Scalar extends Curve.Scalar {} class BaseField extends Curve.BaseField {} const Signature: Struct & (new (value: Signature) => Signature) = diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index 237f7fa9c9..15c48a1897 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -17,7 +17,9 @@ let signature = EthSignature.fromHex( ); let msgHash = - 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn; + Secp256k1.Scalar.from( + 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn + ); console.time('ecdsa verify (witness gen / check)'); Provable.runAndCheck(() => { From b1c504267a4af208193d4ad2974ed1481146ae7a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 5 Jul 2023 11:10:07 -0700 Subject: [PATCH 0098/1215] Revert "refactor(hash.ts): move fromHex and toHex from UInt8 to Hash namespace" This reverts commit 3e24ff7d98a4650cb2982dd382d07b149a817b05. --- src/examples/keccak.ts | 4 ++-- src/lib/hash.ts | 14 -------------- src/lib/int.ts | 14 ++++++++++++++ src/lib/keccak.unit-test.ts | 11 ++++------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts index 7f3a26bc58..243b64996e 100644 --- a/src/examples/keccak.ts +++ b/src/examples/keccak.ts @@ -9,8 +9,8 @@ function equals(a: UInt8[], b: UInt8[]): boolean { function checkDigestHexConversion(digest: UInt8[]) { console.log('Checking hex->digest, digest->hex matches'); Provable.asProver(() => { - const hex = Hash.toHex(digest); - const expected = Hash.fromHex(hex); + const hex = UInt8.toHex(digest); + const expected = UInt8.fromHex(hex); if (equals(digest, expected)) { console.log('✅ Digest matches'); } else { diff --git a/src/lib/hash.ts b/src/lib/hash.ts index c4a08be54c..d1bf656cf8 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -220,18 +220,4 @@ const Hash = { SHA512: buildSHA(512, true), Keccak256: buildSHA(256, false), - - fromHex(xs: string): UInt8[] { - return Snarky.sha - .fieldBytesFromHex(xs) - .map((x) => UInt8.from(Field(x))) - .slice(1); - }, - - toHex(xs: UInt8[]): string { - return xs - .map((x) => x.value) - .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) - .join(''); - }, }; diff --git a/src/lib/int.ts b/src/lib/int.ts index e8cd7ae47b..b479411d64 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1465,6 +1465,20 @@ class UInt8 extends Struct({ return this.value.isConstant(); } + static fromHex(xs: string): UInt8[] { + return Snarky.sha + .fieldBytesFromHex(xs) + .map((x) => UInt8.from(Field(x))) + .slice(1); + } + + static toHex(xs: UInt8[]): string { + return xs + .map((x) => x.value) + .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) + .join(''); + } + /** * Creates a {@link UInt8} with a value of 255. */ diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index e86361d3e5..c934bcf15e 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -65,14 +65,11 @@ function checkHashConversions(data: UInt8[]) { } function expectDigestToEqualHex(digest: UInt8[]) { - const hex = Hash.toHex(digest); - expect(equals(digest, Hash.fromHex(hex))).toBe(true); + const hex = UInt8.toHex(digest); + expect(equals(digest, UInt8.fromHex(hex))).toBe(true); } function equals(a: UInt8[], b: UInt8[]): boolean { - // Provable.log('DEBUG', 'a', a); - // Provable.log('DEBUG', 'b', b); - if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); @@ -249,8 +246,8 @@ function testExpected({ Provable.runAndCheck(() => { assert(message.length % 2 === 0); - let fields = Hash.fromHex(message); - let expectedHash = Hash.fromHex(expected); + let fields = UInt8.fromHex(message); + let expectedHash = UInt8.fromHex(expected); Provable.asProver(() => { if (nist) { From c0a3fa518e7018433559326102a6e405ab438d71 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 5 Jul 2023 11:18:15 -0700 Subject: [PATCH 0099/1215] feat(bindings): update bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a92e783112..1b4781eda9 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a92e7831122164e67e0374b8aac893d738ff6626 +Subproject commit 1b4781eda968929c514f136de66c8b8e1d8fb5bf From 553ef3a85aa5659ee076819ed84693045ae48c0a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 5 Jul 2023 11:40:37 -0700 Subject: [PATCH 0100/1215] feat(uint8): add scaffold for rangeCheck it works for proofs --- src/lib/int.ts | 39 +++++++++++++++++++++++++++++++++------ src/snarky.d.ts | 2 ++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index b479411d64..db6ef9e907 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -980,7 +980,10 @@ class UInt8 extends Struct({ if (x instanceof UInt8) return x; super({ value: Field(x) }); - this.value.toBits(UInt8.NUM_BITS); // Make sure that the Field element that is exactly a byte + + // TODO: Enable when rangeCheck works in proofs + // UInt8.#rangeCheck(this.value); + this.check(); } /** @@ -1125,12 +1128,11 @@ class UInt8 extends Struct({ Field, () => new Field(x.toBigInt() / y_.toBigInt()) ); - // TODO: Need to range check `q` + UInt8.#rangeCheck(q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - - // TODO: Need to range check `r` + UInt8.#rangeCheck(r); let r_ = UInt8.from(r); let q_ = UInt8.from(q); @@ -1160,7 +1162,17 @@ class UInt8 extends Struct({ if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); } else { - // TODO: Need more efficient range checking + // TODO: Enable when rangeCheck works in proofs + // let xMinusY = this.value.sub(y.value).seal(); + // UInt8.#rangeCheck(xMinusY); + + // let yMinusX = xMinusY.neg(); + // UInt8.#rangeCheck(yMinusX); + + // x <= y if y - x fits in 64 bits + // return yMinusX; + + // TODO: Remove this when rangeCheck works in proofs return this.value.lessThanOrEqual(y.value); } } @@ -1227,7 +1239,11 @@ class UInt8 extends Struct({ } return; } - // TODO: Need more efficient range checking + // TODO: Enable when rangeCheck works in proofs + // let yMinusX = value.value.sub(this.value).seal(); + // UInt8.#rangeCheck(yMinusX); + + // TODO: Remove this when rangeCheck works in proofs return this.lessThanOrEqual(value).assertEquals(true, message); } @@ -1376,6 +1392,8 @@ class UInt8 extends Struct({ * @param value - the {@link UInt8} element to check. */ check() { + // TODO: Enable when rangeCheck works in proofs + // UInt8.#rangeCheck(this.value); this.value.toBits(UInt8.NUM_BITS); } @@ -1507,6 +1525,15 @@ class UInt8 extends Struct({ x.toBits(UInt8.NUM_BITS); return x; } + + // TODO: rangeCheck does not prove as of yet, waiting on https://github.com/MinaProtocol/mina/pull/12524 to merge. + static #rangeCheck(x: UInt8 | Field) { + if (isUInt8(x)) x = x.value; + if (x.isConstant()) this.checkConstant(x); + + // Throws an error if the value is not in the range [0, 2^UInt8.NUM_BITS - 1] + Snarky.sha.checkBits(x.value, UInt8.NUM_BITS); + } } function isUInt8(x: unknown): x is UInt8 { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index bba88fb5ab..421ee2d142 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -252,6 +252,8 @@ declare const Snarky: { ): MlArray; fieldBytesFromHex(hex: string): MlArray; + + checkBits(value: FieldVar, bits: number): void; }; poseidon: { From bec639ac6400ebe6b57d140ce30cdc20cae3ac80 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 5 Jul 2023 12:18:39 -0700 Subject: [PATCH 0101/1215] chore(uint8): add rangeCheck todo comments --- src/lib/int.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index db6ef9e907..cf551d479d 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -980,9 +980,6 @@ class UInt8 extends Struct({ if (x instanceof UInt8) return x; super({ value: Field(x) }); - - // TODO: Enable when rangeCheck works in proofs - // UInt8.#rangeCheck(this.value); this.check(); } @@ -1128,11 +1125,15 @@ class UInt8 extends Struct({ Field, () => new Field(x.toBigInt() / y_.toBigInt()) ); - UInt8.#rangeCheck(q); + + // TODO: Enable when rangeCheck works in proofs + // UInt8.#rangeCheck(q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - UInt8.#rangeCheck(r); + + // TODO: Enable when rangeCheck works in proofs + // UInt8.#rangeCheck(r); let r_ = UInt8.from(r); let q_ = UInt8.from(q); From 7cca666dd7894969176b8f08732a20f13b20dfdc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 12 Jul 2023 21:34:21 +0200 Subject: [PATCH 0102/1215] remove bindings.js from prettierignore --- .prettierignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 099c3fecca..c3f051e6bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,3 @@ src/bindings/compiled/web_bindings/**/plonk_wasm* src/bindings/compiled/web_bindings/**/*.bc.js src/bindings/compiled/node_bindings/* dist/**/* -src/bindings/kimchi/js/**/*.js From 912b9d3bfb49550bc99694bd078846ad1e84744f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 12 Jul 2023 21:34:25 +0200 Subject: [PATCH 0103/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7b1fd9d78e..e8b94378cc 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7b1fd9d78e53e2bb37d2ace9ad7e88dbd8f85160 +Subproject commit e8b94378ccf1f2bcd0542b4c8a8ee95809510f3b From 9b5cf28ecfc17cb7cc2d4e8513c3e38ca3cdc994 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 12 Jul 2023 22:23:15 +0200 Subject: [PATCH 0104/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ca49f31b31..e78f2a7db2 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ca49f31b313eb8ee4afb7352fa2c5c94d5105387 +Subproject commit e78f2a7db2210f42da98ffbcabcae3a3c4a5bdb5 From b53080322edd063bbcaa4f4bd5194da876735f8d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 12 Jul 2023 23:21:15 +0200 Subject: [PATCH 0105/1215] renamings --- src/lib/foreign-curve.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 68f392dc64..818895c69a 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -37,7 +37,7 @@ function toMl({ x, y }: Affine): ForeignCurveVar { type ForeignCurveClass = ReturnType; function createForeignCurve(curve: CurveParams) { - const curveMl = Snarky.foreignCurve.create(MlCurveParams(curve)); + const curveParamsMl = Snarky.foreignCurve.create(MlCurveParams(curve)); const curveName = curve.name; class BaseField extends createForeignField(curve.modulus) {} @@ -77,17 +77,20 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(g); } - static #curveMlVar: unknown | undefined; + static #curveParamsMlVar: unknown | undefined; + static initialize() { - ForeignCurve.#curveMlVar = Snarky.foreignCurve.paramsToVars(curveMl); + ForeignCurve.#curveParamsMlVar = + Snarky.foreignCurve.paramsToVars(curveParamsMl); } + static _getParams(name: string): unknown { - if (ForeignCurve.#curveMlVar === undefined) { + if (ForeignCurve.#curveParamsMlVar === undefined) { throw Error( `${name}(): You must call ${this.name}.initialize() once per provable method to use ${curveName}.` ); } - return ForeignCurve.#curveMlVar; + return ForeignCurve.#curveParamsMlVar; } static generator = new ForeignCurve(curve.gen); From afce0659a1d7a893dd60a6ac69d4485735b85418 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 13 Jul 2023 02:37:18 +0200 Subject: [PATCH 0106/1215] add most constant curve impls --- src/bindings | 2 +- src/lib/foreign-curve.ts | 43 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index e78f2a7db2..2c468a0327 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e78f2a7db2210f42da98ffbcabcae3a3c4a5bdb5 +Subproject commit 2c468a0327320362eeb6c2d3d2b24e523972db12 diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 818895c69a..29f9cebd28 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,6 +1,7 @@ +import { createCurveAffine } from '../bindings/crypto/elliptic_curve.js'; import { Snarky } from '../snarky.js'; import { Bool } from './bool.js'; -import { Struct } from './circuit_value.js'; +import { Struct, isConstant } from './circuit_value.js'; import { ForeignField, ForeignFieldConst, @@ -47,6 +48,13 @@ function createForeignCurve(curve: CurveParams) { // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. const Affine: Struct = Struct({ x: BaseField, y: BaseField }); + const ConstantCurve = createCurveAffine({ + p: curve.modulus, + a: curve.a, + b: curve.b, + generator: curve.gen, + }); + return class ForeignCurve extends Affine { constructor( g: @@ -95,30 +103,63 @@ function createForeignCurve(curve: CurveParams) { static generator = new ForeignCurve(curve.gen); + isConstant() { + return isConstant(ForeignCurve, this); + } + + toBigint() { + return { x: this.x.toBigInt(), y: this.y.toBigInt() }; + } + #toConstant() { + return { ...this.toBigint(), infinity: false }; + } + add( h: | ForeignCurve | { x: BaseField | bigint | number; y: BaseField | bigint | number } ) { let h_ = ForeignCurve.from(h); + if (this.isConstant() && h_.isConstant()) { + let z = ConstantCurve.add(this.#toConstant(), h_.#toConstant()); + return new ForeignCurve(z); + } let curve = ForeignCurve._getParams(`${this.constructor.name}.add`); let p = Snarky.foreignCurve.add(toMl(this), toMl(h_), curve); return new ForeignCurve(p); } double() { + if (this.isConstant()) { + let z = ConstantCurve.double(this.#toConstant()); + return new ForeignCurve(z); + } let curve = ForeignCurve._getParams(`${this.constructor.name}.double`); let p = Snarky.foreignCurve.double(toMl(this), curve); return new ForeignCurve(p); } negate() { + if (this.isConstant()) { + let z = ConstantCurve.negate(this.#toConstant()); + return new ForeignCurve(z); + } let curve = ForeignCurve._getParams(`${this.constructor.name}.negate`); let p = Snarky.foreignCurve.negate(toMl(this), curve); return new ForeignCurve(p); } assertOnCurve() { + if (this.isConstant()) { + let isOnCurve = ConstantCurve.isOnCurve(this.#toConstant()); + if (!isOnCurve) + throw Error( + `${this.constructor.name}.assertOnCurve(): ${JSON.stringify( + this + )} is not on the curve.` + ); + return; + } let curve = ForeignCurve._getParams( `${this.constructor.name}.assertOnCurve` ); From 6b43f92af710fdfa987f9a315a99e6a8f2454ea0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 10:48:18 +0200 Subject: [PATCH 0107/1215] scale --- src/bindings | 2 +- src/lib/foreign-curve.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 2c468a0327..c279a27356 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 2c468a0327320362eeb6c2d3d2b24e523972db12 +Subproject commit c279a273565881e885468e5b3cefd72de433439d diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 29f9cebd28..176e08e7c6 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -168,6 +168,11 @@ function createForeignCurve(curve: CurveParams) { // TODO wrap this in a `Scalar` type which is a Bool array under the hood? scale(scalar: Bool[]) { + if (this.isConstant() && scalar.every((b) => b.isConstant())) { + let scalar0 = scalar.map((b) => b.toBoolean()); + let z = ConstantCurve.scale(this.#toConstant(), scalar0); + return new ForeignCurve(z); + } let curve = ForeignCurve._getParams(`${this.constructor.name}.scale`); let p = Snarky.foreignCurve.scale( toMl(this), From 5781049686a14a5945cf5dcedfdb495225486d10 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 11:14:52 +0200 Subject: [PATCH 0108/1215] constant subgroup check --- src/bindings | 2 +- src/lib/foreign-curve.ts | 15 +++++++++++++-- src/lib/foreign-curve.unit-test.ts | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/bindings b/src/bindings index c279a27356..343f3640a2 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c279a273565881e885468e5b3cefd72de433439d +Subproject commit 343f3640a252ca6a7c7dcb0041f6d9636fd163ef diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 176e08e7c6..c9d270bf5a 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -50,6 +50,7 @@ function createForeignCurve(curve: CurveParams) { const ConstantCurve = createCurveAffine({ p: curve.modulus, + order: curve.order, a: curve.a, b: curve.b, generator: curve.gen, @@ -182,8 +183,18 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } - checkSubgroup() { - let curve = ForeignCurve._getParams(`${curveName}.checkSubgroup`); + assertInSubgroup() { + if (this.isConstant()) { + let isInGroup = ConstantCurve.isInSubgroup(this.#toConstant()); + if (!isInGroup) + throw Error( + `${this.constructor.name}.assertInSubgroup(): ${JSON.stringify( + this + )} is not in the target subgroup.` + ); + return; + } + let curve = ForeignCurve._getParams(`${curveName}.assertInSubgroup`); Snarky.foreignCurve.checkSubgroup(toMl(this), curve); } diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 79bfd12cd7..6c57ae1a8e 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -21,7 +21,7 @@ function main() { h0.assertOnCurve(); // TODO super slow - // h0.checkSubgroup(); + // h0.assertInSubgroup(); let scalar0 = Provable.witness(Field, () => new Field(scalar)).toBits(); // TODO super slow From 5ad6b491593e3c42763eee20be6b1fda861470c4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 11:30:56 +0200 Subject: [PATCH 0109/1215] getting non provable curve ops to run --- src/lib/foreign-curve.ts | 27 ++++++++++++++++----------- src/lib/foreign-curve.unit-test.ts | 13 +++++++++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index c9d270bf5a..080b8b3a5b 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -10,6 +10,7 @@ import { } from './foreign-field.js'; import { MlBigint } from './ml/base.js'; import { MlBoolArray } from './ml/fields.js'; +import { inCheckedComputation } from './provable-context.js'; // external API export { createForeignCurve, CurveParams }; @@ -56,7 +57,11 @@ function createForeignCurve(curve: CurveParams) { generator: curve.gen, }); - return class ForeignCurve extends Affine { + function toConstant(x: ForeignCurve) { + return { ...x.toBigint(), infinity: false }; + } + + class ForeignCurve extends Affine { constructor( g: | { x: BaseField | bigint | number; y: BaseField | bigint | number } @@ -89,6 +94,7 @@ function createForeignCurve(curve: CurveParams) { static #curveParamsMlVar: unknown | undefined; static initialize() { + if (!inCheckedComputation()) return; ForeignCurve.#curveParamsMlVar = Snarky.foreignCurve.paramsToVars(curveParamsMl); } @@ -111,9 +117,6 @@ function createForeignCurve(curve: CurveParams) { toBigint() { return { x: this.x.toBigInt(), y: this.y.toBigInt() }; } - #toConstant() { - return { ...this.toBigint(), infinity: false }; - } add( h: @@ -122,7 +125,7 @@ function createForeignCurve(curve: CurveParams) { ) { let h_ = ForeignCurve.from(h); if (this.isConstant() && h_.isConstant()) { - let z = ConstantCurve.add(this.#toConstant(), h_.#toConstant()); + let z = ConstantCurve.add(toConstant(this), toConstant(h_)); return new ForeignCurve(z); } let curve = ForeignCurve._getParams(`${this.constructor.name}.add`); @@ -132,7 +135,7 @@ function createForeignCurve(curve: CurveParams) { double() { if (this.isConstant()) { - let z = ConstantCurve.double(this.#toConstant()); + let z = ConstantCurve.double(toConstant(this)); return new ForeignCurve(z); } let curve = ForeignCurve._getParams(`${this.constructor.name}.double`); @@ -142,7 +145,7 @@ function createForeignCurve(curve: CurveParams) { negate() { if (this.isConstant()) { - let z = ConstantCurve.negate(this.#toConstant()); + let z = ConstantCurve.negate(toConstant(this)); return new ForeignCurve(z); } let curve = ForeignCurve._getParams(`${this.constructor.name}.negate`); @@ -152,7 +155,7 @@ function createForeignCurve(curve: CurveParams) { assertOnCurve() { if (this.isConstant()) { - let isOnCurve = ConstantCurve.isOnCurve(this.#toConstant()); + let isOnCurve = ConstantCurve.isOnCurve(toConstant(this)); if (!isOnCurve) throw Error( `${this.constructor.name}.assertOnCurve(): ${JSON.stringify( @@ -171,7 +174,7 @@ function createForeignCurve(curve: CurveParams) { scale(scalar: Bool[]) { if (this.isConstant() && scalar.every((b) => b.isConstant())) { let scalar0 = scalar.map((b) => b.toBoolean()); - let z = ConstantCurve.scale(this.#toConstant(), scalar0); + let z = ConstantCurve.scale(toConstant(this), scalar0); return new ForeignCurve(z); } let curve = ForeignCurve._getParams(`${this.constructor.name}.scale`); @@ -185,7 +188,7 @@ function createForeignCurve(curve: CurveParams) { assertInSubgroup() { if (this.isConstant()) { - let isInGroup = ConstantCurve.isInSubgroup(this.#toConstant()); + let isInGroup = ConstantCurve.isInSubgroup(toConstant(this)); if (!isInGroup) throw Error( `${this.constructor.name}.assertInSubgroup(): ${JSON.stringify( @@ -200,7 +203,9 @@ function createForeignCurve(curve: CurveParams) { static BaseField = BaseField; static Scalar = ScalarField; - }; + } + + return ForeignCurve; } /** diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 6c57ae1a8e..b7c1ebf8e8 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -25,12 +25,21 @@ function main() { let scalar0 = Provable.witness(Field, () => new Field(scalar)).toBits(); // TODO super slow - // let p0 = h0.scale(scalar0); - // Provable.assertEqual(Vesta, p0, new Vesta(p)); + let p0 = h0.scale(scalar0); + Provable.assertEqual(Vesta, p0, new Vesta(p)); } +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +console.time('running witness generation & checks'); Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); let { gates } = Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); let gateTypes: Record = {}; for (let gate of gates) { From 78c635b324ac5acd1e1149e456a6cec897d3f729 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 11:53:59 +0200 Subject: [PATCH 0110/1215] start writing doc comments --- src/bindings | 2 +- src/lib/foreign-curve.ts | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 343f3640a2..96ee3e4807 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 343f3640a252ca6a7c7dcb0041f6d9636fd163ef +Subproject commit 96ee3e4807bb11e1557fc7ca2c0375c371287528 diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 080b8b3a5b..92e7356b7b 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,6 +1,7 @@ import { createCurveAffine } from '../bindings/crypto/elliptic_curve.js'; import { Snarky } from '../snarky.js'; import { Bool } from './bool.js'; +import type { Group } from './group.js'; import { Struct, isConstant } from './circuit_value.js'; import { ForeignField, @@ -38,6 +39,29 @@ function toMl({ x, y }: Affine): ForeignCurveVar { type ForeignCurveClass = ReturnType; +/** + * Create a class representing an elliptic curve group, which is different from the native {@link Group}. + * + * ```ts + * const Curve = createForeignCurve(secp256k1Params); // the elliptic curve 'secp256k1' + * ``` + * + * `createForeignCurve(params)` takes the curve parameters {@link CurveParams} as input. + * We support `modulus` and `order` to be prime numbers to 259 bits. + * + * The returned {@link ForeignCurve} class supports standard elliptic curve operations like point addition and scalar multiplication. + * It also includes to associated foreign fields: `ForeignCurve.BaseField` and `ForeignCurve.Scalar`, see {@link createForeignField}. + * + * _Advanced usage:_ + * + * To skip automatic validity checks when introducing curve points and scalars into provable code, + * use the optional `{ unsafe: true }` configuration. See {@link createForeignField} for details. + * This option is applied to both the scalar field and the base field. + * + * @param curve parameters for the elliptic curve you are instantiating + * @param options + * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. + */ function createForeignCurve(curve: CurveParams) { const curveParamsMl = Snarky.foreignCurve.create(MlCurveParams(curve)); const curveName = curve.name; @@ -62,6 +86,15 @@ function createForeignCurve(curve: CurveParams) { } class ForeignCurve extends Affine { + /** + * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. + * @example + * ```ts + * let x = new ForeignCurve({ x: 1n, y: 1n }); + * ``` + * + * **Warning**: This fails for a constant input which does not represent an actual point on the curve. + */ constructor( g: | { x: BaseField | bigint | number; y: BaseField | bigint | number } @@ -80,6 +113,8 @@ function createForeignCurve(curve: CurveParams) { y_ = BaseField.from(y); } super({ x: x_, y: y_ }); + // don't allow constants that aren't on the curve + if (this.isConstant()) this.assertOnCurve(); } static from( @@ -159,7 +194,7 @@ function createForeignCurve(curve: CurveParams) { if (!isOnCurve) throw Error( `${this.constructor.name}.assertOnCurve(): ${JSON.stringify( - this + ForeignCurve.toJSON(this) )} is not on the curve.` ); return; From bf9968a152d04253fb26d147a097c479c321f961 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 19:35:04 +0200 Subject: [PATCH 0111/1215] more doc comments, unsafe parameter, proper check() --- src/lib/foreign-curve.ts | 107 +++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 21 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 92e7356b7b..45c66cda30 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -62,12 +62,13 @@ type ForeignCurveClass = ReturnType; * @param options * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. */ -function createForeignCurve(curve: CurveParams) { +function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { const curveParamsMl = Snarky.foreignCurve.create(MlCurveParams(curve)); const curveName = curve.name; + const hasCofactor = curve.cofactor !== undefined && curve.cofactor !== 1n; - class BaseField extends createForeignField(curve.modulus) {} - class ScalarField extends createForeignField(curve.order) {} + class BaseField extends createForeignField(curve.modulus, { unsafe }) {} + class ScalarField extends createForeignField(curve.order, { unsafe }) {} // this is necessary to simplify the type of ForeignCurve, to avoid // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. @@ -81,8 +82,11 @@ function createForeignCurve(curve: CurveParams) { generator: curve.gen, }); - function toConstant(x: ForeignCurve) { - return { ...x.toBigint(), infinity: false }; + function toBigint(g: Affine) { + return { x: g.x.toBigInt(), y: g.y.toBigInt() }; + } + function toConstant(g: Affine) { + return { ...toBigint(g), infinity: false }; } class ForeignCurve extends Affine { @@ -117,6 +121,9 @@ function createForeignCurve(curve: CurveParams) { if (this.isConstant()) this.assertOnCurve(); } + /** + * Coerce the input to a {@link ForeignCurve}. + */ static from( g: | ForeignCurve @@ -128,6 +135,9 @@ function createForeignCurve(curve: CurveParams) { static #curveParamsMlVar: unknown | undefined; + /** + * Initialize usage of the curve. This function has to be called oncel per provable method to use the curve. + */ static initialize() { if (!inCheckedComputation()) return; ForeignCurve.#curveParamsMlVar = @@ -145,14 +155,25 @@ function createForeignCurve(curve: CurveParams) { static generator = new ForeignCurve(curve.gen); + /** + * Checks whether this curve point is constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ isConstant() { return isConstant(ForeignCurve, this); } + /** + * Convert this curve point to a point with bigint coordinates. + */ toBigint() { return { x: this.x.toBigInt(), y: this.y.toBigInt() }; } + /** + * Elliptic curve addition. + */ add( h: | ForeignCurve @@ -168,6 +189,9 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } + /** + * Elliptic curve doubling. + */ double() { if (this.isConstant()) { let z = ConstantCurve.double(toConstant(this)); @@ -178,6 +202,9 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } + /** + * Elliptic curve negation. + */ negate() { if (this.isConstant()) { let z = ConstantCurve.negate(toConstant(this)); @@ -188,24 +215,34 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } - assertOnCurve() { - if (this.isConstant()) { - let isOnCurve = ConstantCurve.isOnCurve(toConstant(this)); + static #assertOnCurve(g: Affine) { + if (isConstant(ForeignCurve, g)) { + let isOnCurve = ConstantCurve.isOnCurve(toConstant(g)); if (!isOnCurve) throw Error( - `${this.constructor.name}.assertOnCurve(): ${JSON.stringify( - ForeignCurve.toJSON(this) + `${this.name}.assertOnCurve(): ${JSON.stringify( + ForeignCurve.toJSON(g) )} is not on the curve.` ); return; } - let curve = ForeignCurve._getParams( - `${this.constructor.name}.assertOnCurve` - ); - Snarky.foreignCurve.assertOnCurve(toMl(this), curve); + let curve = ForeignCurve._getParams(`${this.name}.assertOnCurve`); + Snarky.foreignCurve.assertOnCurve(toMl(g), curve); + } + + /** + * Assert that this point lies on the elliptic curve, which means it satisfies the equation + * y^2 = x^3 + ax + b + */ + assertOnCurve() { + ForeignCurve.#assertOnCurve(this); } // TODO wrap this in a `Scalar` type which is a Bool array under the hood? + /** + * Elliptic curve scalar multiplication, where the scalar is represented as a little-endian + * array of bits, and each bit is represented by a {@link Bool}. + */ scale(scalar: Bool[]) { if (this.isConstant() && scalar.every((b) => b.isConstant())) { let scalar0 = scalar.map((b) => b.toBoolean()); @@ -221,19 +258,41 @@ function createForeignCurve(curve: CurveParams) { return new ForeignCurve(p); } - assertInSubgroup() { - if (this.isConstant()) { - let isInGroup = ConstantCurve.isInSubgroup(toConstant(this)); + static #assertInSubgroup(g: Affine) { + if (isConstant(Affine, g)) { + let isInGroup = ConstantCurve.isInSubgroup(toConstant(g)); if (!isInGroup) throw Error( - `${this.constructor.name}.assertInSubgroup(): ${JSON.stringify( - this + `${this.name}.assertInSubgroup(): ${JSON.stringify( + g )} is not in the target subgroup.` ); return; } - let curve = ForeignCurve._getParams(`${curveName}.assertInSubgroup`); - Snarky.foreignCurve.checkSubgroup(toMl(this), curve); + let curve_ = ForeignCurve._getParams(`${curveName}.assertInSubgroup`); + Snarky.foreignCurve.checkSubgroup(toMl(g), curve_); + } + + /** + * Assert than this point lies in the subgroup defined by order*P = 0, + * by performing the scalar multiplication. + */ + assertInSubgroup() { + ForeignCurve.#assertInSubgroup(this); + } + + /** + * Check that this is a valid element of the target subgroup of the curve: + * - Use {@link assertOnCurve()} to check that the point lies on the curve + * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. + * + * **Exception**: If {@link createForeignCurve} is called with `{ unsafe: true }`, + * we don't check that curve elements are valid by default. + */ + static check(g: Affine) { + if (unsafe) return; + ForeignCurve.#assertOnCurve(g); + if (hasCofactor) ForeignCurve.#assertInSubgroup(g); } static BaseField = BaseField; @@ -260,6 +319,12 @@ type CurveParams = { * Scalar field modulus = group order */ order: bigint; + /** + * Cofactor = size of EC / order + * + * This can be left undefined if the cofactor is 1. + */ + cofactor?: bigint; /** * The `a` parameter in the curve equation y^2 = x^3 + ax + b */ From 61bb48568700253665f5fe413ff001a21469be80 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 20:13:42 +0200 Subject: [PATCH 0112/1215] fox typo and foreign curve check --- src/lib/foreign-curve.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 45c66cda30..b3f03b8a81 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -136,7 +136,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { static #curveParamsMlVar: unknown | undefined; /** - * Initialize usage of the curve. This function has to be called oncel per provable method to use the curve. + * Initialize usage of the curve. This function has to be called once per provable method to use the curve. */ static initialize() { if (!inCheckedComputation()) return; @@ -291,6 +291,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { */ static check(g: Affine) { if (unsafe) return; + super.check(g); // check that x, y are valid field elements ForeignCurve.#assertOnCurve(g); if (hasCofactor) ForeignCurve.#assertInSubgroup(g); } From 499928d89abc874c5887153c7524d1bed59c2ba4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 20:29:27 +0200 Subject: [PATCH 0113/1215] ecdsa doc comments --- src/lib/foreign-ecdsa.ts | 46 +++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index aafafe2ed5..540991d0e7 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,5 +1,5 @@ import { Snarky } from '../snarky.js'; -import { Struct } from './circuit_value.js'; +import { Struct, isConstant } from './circuit_value.js'; import { CurveParams, ForeignCurveClass, @@ -23,6 +23,10 @@ function signatureToMl({ r, s }: Signature): ForeignSignatureVar { return [0, r.value, s.value]; } +/** + * Returns a class {@link EcdsaSignature} enabling to parse and verify ECDSA signature in provable code, + * for the given curve. + */ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let Curve0: ForeignCurveClass = 'gen' in curve ? createForeignCurve(curve) : curve; @@ -30,28 +34,35 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { class Scalar extends Curve.Scalar {} class BaseField extends Curve.BaseField {} - const Signature: Struct & (new (value: Signature) => Signature) = - Struct({ r: Scalar, s: Scalar }); + const Signature: Struct = Struct({ r: Scalar, s: Scalar }); class EcdsaSignature extends Signature { static Curve = Curve0; - static from({ - r, - s, - }: { + /** + * Coerce the input to a {@link EcdsaSignature}. + */ + static from(signature: { r: Scalar | bigint; s: Scalar | bigint; }): EcdsaSignature { - return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); + if (signature instanceof EcdsaSignature) return signature; + return new EcdsaSignature({ + r: Scalar.from(signature.r), + s: Scalar.from(signature.s), + }); } + /** + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ static fromHex(rawSignature: string): EcdsaSignature { let prefix = rawSignature.slice(0, 2); let signature = rawSignature.slice(2, 130); if (prefix !== '0x' || signature.length < 128) { throw Error( - `${this.constructor.name}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` + `${this.name}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` ); } let r = BigInt(`0x${signature.slice(0, 64)}`); @@ -59,6 +70,11 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); } + /** + * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * + * This method proves that the signature is valid, and throws if it isn't. + */ verify( msgHash: Scalar | bigint, publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } @@ -70,8 +86,18 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { Snarky.ecdsa.verify(signatureMl, msgHashMl, publicKeyMl, curve); } + /** + * Check that r, s are valid scalars and both are non-zero + */ static check(signature: { r: Scalar; s: Scalar }) { - let curve = Curve0._getParams(`${this.constructor.name}.check`); + if (isConstant(Signature, signature)) { + super.check(signature); // check valid scalars + if (signature.r.toBigInt() === 0n) + throw Error(`${this.name}.check(): r must be non-zero`); + if (signature.s.toBigInt() === 0n) + throw Error(`${this.name}.check(): s must be non-zero`); + } + let curve = Curve0._getParams(`${this.name}.check`); let signatureMl = signatureToMl(signature); Snarky.ecdsa.assertValidSignature(signatureMl, curve); } From f2460f1760365751a13f1ebfc03b5532830c8845 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 21:54:39 +0200 Subject: [PATCH 0114/1215] implement ecdsa in ts for non-provable --- src/bindings | 2 +- src/lib/foreign-curve.ts | 1 + src/lib/foreign-ecdsa.ts | 50 ++++++++++++++++++++++++++++-- src/lib/foreign-ecdsa.unit-test.ts | 20 ++++++------ 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/bindings b/src/bindings index 96ee3e4807..319f69164d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 96ee3e4807bb11e1557fc7ca2c0375c371287528 +Subproject commit 319f69164d95ccc526752c948345ebf1338f913c diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index b3f03b8a81..85fc6fbc62 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -298,6 +298,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { static BaseField = BaseField; static Scalar = ScalarField; + static Bigint = ConstantCurve; } return ForeignCurve; diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 540991d0e7..3c2f229692 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,3 +1,5 @@ +import { inverse, mod } from '../bindings/crypto/finite_field.js'; +import { CurveAffine } from '../bindings/crypto/elliptic_curve.js'; import { Snarky } from '../snarky.js'; import { Struct, isConstant } from './circuit_value.js'; import { @@ -79,10 +81,26 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { msgHash: Scalar | bigint, publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } ): void { + let msgHash_ = Scalar.from(msgHash); + let publicKey_ = Curve.from(publicKey); + + if (isConstant(Signature, this)) { + let signature = { r: this.r.toBigInt(), s: this.s.toBigInt() }; + let isValid = verifyEcdsa( + Curve.Bigint, + signature, + msgHash_.toBigInt(), + publicKey_.toBigint() + ); + if (!isValid) { + throw Error(`${this.constructor.name}.verify(): Invalid signature.`); + } + return; + } let curve = Curve0._getParams(`${this.constructor.name}.verify`); let signatureMl = signatureToMl(this); - let msgHashMl = Scalar.from(msgHash).value; - let publicKeyMl = affineToMl(Curve.from(publicKey)); + let msgHashMl = msgHash_.value; + let publicKeyMl = affineToMl(publicKey_); Snarky.ecdsa.verify(signatureMl, msgHashMl, publicKeyMl, curve); } @@ -96,6 +114,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { throw Error(`${this.name}.check(): r must be non-zero`); if (signature.s.toBigInt() === 0n) throw Error(`${this.name}.check(): s must be non-zero`); + return; } let curve = Curve0._getParams(`${this.name}.check`); let signatureMl = signatureToMl(signature); @@ -107,3 +126,30 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { return EcdsaSignature; } + +/** + * Bigint implementation of ECDSA verify + */ +function verifyEcdsa( + Curve: CurveAffine, + { r, s }: { r: bigint; s: bigint }, + msgHash: bigint, + publicKey: { x: bigint; y: bigint } +) { + let q = Curve.order; + let QA = Curve.fromNonzero(publicKey); + if (!Curve.isOnCurve(QA)) return false; + // TODO subgroup check conditional on whether there is a cofactor + if (r < 1n || r >= Curve.order) return false; + if (s < 1n || s >= Curve.order) return false; + + let sInv = inverse(s, q); + if (sInv === undefined) throw Error('impossible'); + let u1 = mod(msgHash * sInv, q); + let u2 = mod(r * sInv, q); + + let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); + if (Curve.equal(X, Curve.zero)) return false; + + return mod(X.x, q) === r; +} diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index 15c48a1897..fd6fd69062 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -21,22 +21,22 @@ let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn ); -console.time('ecdsa verify (witness gen / check)'); -Provable.runAndCheck(() => { +function main() { Secp256k1.initialize(); let signature0 = Provable.witness(EthSignature, () => signature); - signature0.verify(msgHash, publicKey); -}); +} + +console.time('ecdsa verify (constant)'); +main(); +console.timeEnd('ecdsa verify (constant)'); + +console.time('ecdsa verify (witness gen / check)'); +Provable.runAndCheck(main); console.timeEnd('ecdsa verify (witness gen / check)'); console.time('ecdsa verify (build constraint system)'); -let cs = Provable.constraintSystem(() => { - Secp256k1.initialize(); - let signature0 = Provable.witness(EthSignature, () => signature); - - signature0.verify(msgHash, publicKey); -}); +let cs = Provable.constraintSystem(main); console.timeEnd('ecdsa verify (build constraint system)'); let gateTypes: Record = {}; From 07a45f70a2bb264f4003c9b117164ee9b52bce7f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 21:55:35 +0200 Subject: [PATCH 0115/1215] minor --- src/lib/foreign-ecdsa.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 3c2f229692..b150dcfa90 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -85,10 +85,9 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let publicKey_ = Curve.from(publicKey); if (isConstant(Signature, this)) { - let signature = { r: this.r.toBigInt(), s: this.s.toBigInt() }; let isValid = verifyEcdsa( Curve.Bigint, - signature, + { r: this.r.toBigInt(), s: this.s.toBigInt() }, msgHash_.toBigInt(), publicKey_.toBigint() ); From 6c01efe849df42971caef8f4bf3b4013df33c8ee Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 13 Jul 2023 22:08:36 +0200 Subject: [PATCH 0116/1215] subgroup check in constant verification --- src/bindings | 2 +- src/lib/foreign-curve.ts | 3 +-- src/lib/foreign-ecdsa.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/bindings b/src/bindings index 319f69164d..c3868b2d7c 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 319f69164d95ccc526752c948345ebf1338f913c +Subproject commit c3868b2d7c5a4df5ad934a39ffb4e75f3325fc8b diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 85fc6fbc62..bf2733d05d 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -65,7 +65,6 @@ type ForeignCurveClass = ReturnType; function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { const curveParamsMl = Snarky.foreignCurve.create(MlCurveParams(curve)); const curveName = curve.name; - const hasCofactor = curve.cofactor !== undefined && curve.cofactor !== 1n; class BaseField extends createForeignField(curve.modulus, { unsafe }) {} class ScalarField extends createForeignField(curve.order, { unsafe }) {} @@ -293,7 +292,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { if (unsafe) return; super.check(g); // check that x, y are valid field elements ForeignCurve.#assertOnCurve(g); - if (hasCofactor) ForeignCurve.#assertInSubgroup(g); + if (ConstantCurve.hasCofactor) ForeignCurve.#assertInSubgroup(g); } static BaseField = BaseField; diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index b150dcfa90..f71a688a1e 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -138,7 +138,7 @@ function verifyEcdsa( let q = Curve.order; let QA = Curve.fromNonzero(publicKey); if (!Curve.isOnCurve(QA)) return false; - // TODO subgroup check conditional on whether there is a cofactor + if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; if (r < 1n || r >= Curve.order) return false; if (s < 1n || s >= Curve.order) return false; From 2dce0578e0487c6df9787b2f3c42e29372c3c4a5 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 14 Jul 2023 01:32:57 +0200 Subject: [PATCH 0117/1215] web compatible benchmark --- src/examples/benchmarks/foreign-curve.ts | 46 ++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 47 insertions(+) create mode 100644 src/examples/benchmarks/foreign-curve.ts diff --git a/src/examples/benchmarks/foreign-curve.ts b/src/examples/benchmarks/foreign-curve.ts new file mode 100644 index 0000000000..4c8178b7e3 --- /dev/null +++ b/src/examples/benchmarks/foreign-curve.ts @@ -0,0 +1,46 @@ +import { createForeignCurve, vestaParams, Provable, Field } from 'snarkyjs'; + +class Vesta extends createForeignCurve(vestaParams) {} + +let g = new Vesta({ x: -1n, y: 2n }); +let scalar = Field.random(); +let h = g.add(Vesta.generator).double().negate(); +let p = h.scale(scalar.toBits()); + +function main() { + Vesta.initialize(); + let g0 = Provable.witness(Vesta, () => g); + let one = Provable.witness(Vesta, () => Vesta.generator); + let h0 = g0.add(one).double().negate(); + Provable.assertEqual(Vesta, h0, new Vesta(h)); + + h0.assertOnCurve(); + // TODO super slow + // h0.assertInSubgroup(); + + let scalar0 = Provable.witness(Field, () => scalar).toBits(); + // TODO super slow + let p0 = h0.scale(scalar0); + Provable.assertEqual(Vesta, p0, p); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +// half of this time is spent in `field_to_bignum_bigint`, which is mostly addition of zarith bigints -.- +console.time('running witness generation & checks'); +Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let { gates } = Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +let gateTypes: Record = {}; +for (let gate of gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; +} + +console.log(gateTypes); diff --git a/src/index.ts b/src/index.ts index 68a95513cd..5ed9d4cad1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export { Field, Bool, Group, Scalar } from './lib/core.js'; export { createForeignField, ForeignField } from './lib/foreign-field.js'; export { createForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa } from './lib/foreign-ecdsa.js'; +export { vestaParams, secp256k1Params } from './lib/foreign-curve-params.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export type { From 343747916fa60a6d6aa1fe755a446910abaef947 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 14 Jul 2023 12:40:44 +0200 Subject: [PATCH 0118/1215] foreign field benchmark --- src/examples/benchmarks/foreign-field.ts | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/examples/benchmarks/foreign-field.ts diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts new file mode 100644 index 0000000000..c785a32d90 --- /dev/null +++ b/src/examples/benchmarks/foreign-field.ts @@ -0,0 +1,35 @@ +import { Scalar, vestaParams, Provable, createForeignField } from 'snarkyjs'; + +class ForeignScalar extends createForeignField(vestaParams.modulus) {} + +// TODO ForeignField.random() +function random() { + return new ForeignScalar(Scalar.random().toBigInt()); +} + +function main() { + let s = Provable.witness(ForeignScalar, random); + let t = Provable.witness(ForeignScalar, random); + s.mul(t); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +// half of this time is spent in `field_to_bignum_bigint`, which is mostly addition of zarith bigints -.- +console.time('running witness generation & checks'); +Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let { gates } = Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +let gateTypes: Record = {}; +for (let gate of gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; +} + +console.log(gateTypes); From 77ccc7c65061a84a5b184a823ac4ac659fab01fc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 15 Jul 2023 10:37:00 -0700 Subject: [PATCH 0119/1215] fix(hash.ts): use MlArray.to() instead of array literal for Snarky.sha.create() --- src/lib/hash.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 4f78af1f2f..347f3f9194 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -7,6 +7,7 @@ import { MlFieldArray } from './ml/fields.js'; import { UInt8 } from './int.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; +import { MlArray } from './ml/base.js'; // external API export { Poseidon, TokenSymbol, Hash }; @@ -212,7 +213,7 @@ function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { return { hash(message: UInt8[]): UInt8[] { return Snarky.sha - .create([0, ...message.map((f) => f.toField().value)], nist, length) + .create(MlArray.to(message.map((f) => f.toField().value)), nist, length) .map((f) => UInt8.from(Field(f))) .slice(1); }, From e73d5bf2a056cd8f6fe26f541005b929dcf4a1c0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 15 Jul 2023 10:37:14 -0700 Subject: [PATCH 0120/1215] chore(bindings): update bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 276f6ef96b..021febf931 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 276f6ef96b91986f10519d313722e59be79ce36f +Subproject commit 021febf9316f873b39af6f9b8582d9f7a30f6c23 From 0090560836af90828d7883b9983ddae92274bc4e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Sep 2023 20:05:53 +0200 Subject: [PATCH 0121/1215] temporarily disable unit test check --- src/lib/foreign-field.unit-test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index e2a6418a08..ceee18df6e 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -151,7 +151,8 @@ test( let gateTypes = gates.map((g) => g.type); // check that gates without generics are as expected - expect(gateTypes).toEqual(expectedGateTypes); + // TODO: reenable after adapting to new gadget layout! + // expect(gateTypes).toEqual(expectedGateTypes); // check that generic gates correspond to adding one of the constants 0, 1 and 2^88 (the limb size) let allowedConstants = new Set([0n, 1n, 1n << 88n]); From 2f08a08749ac22093a172d9551ad16c9b1987b97 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Sep 2023 20:20:30 +0200 Subject: [PATCH 0122/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 9a8b93d3a4..8fe9971506 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9a8b93d3a4317536cfcabc3e3098d2772c21ad72 +Subproject commit 8fe99715066c51926abf9cd92ab8c096d0135d7d From dec549e6da3e50faf9fe5ab841eb8b346062ba1a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Sep 2023 08:53:29 +0200 Subject: [PATCH 0123/1215] enable proof test --- src/lib/foreign-field.unit-test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index ceee18df6e..4bf5973d37 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -227,11 +227,11 @@ let { rows: rows1 } = Provable.constraintSystem(main1); expect(rows0 + 100).toBeLessThan(rows1); // TODO: add when proving works -/* + class Main extends Circuit { @circuitMain static main() { - main_(); + main0(); } } @@ -252,7 +252,7 @@ let Program = ZkProgram({ test: { privateInputs: [], method() { - main_(); + main0(); }, }, }, @@ -266,7 +266,6 @@ let proof = await Program.test(); ok = await Program.verify(proof); console.log('verifies?', ok); - */ type GateType = | 'Zero' From 9ee0b016905d37ed2354c7b6fe0604fd4c339ee5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Sep 2023 12:52:59 +0200 Subject: [PATCH 0124/1215] tweak foreign field test --- src/lib/foreign-field.unit-test.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 4bf5973d37..f77d281cdf 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -226,12 +226,20 @@ let { rows: rows0 } = Provable.constraintSystem(main0); let { rows: rows1 } = Provable.constraintSystem(main1); expect(rows0 + 100).toBeLessThan(rows1); -// TODO: add when proving works +// tests with proving + +function simpleMain() { + let s = Provable.witness( + ForeignScalar, + () => new ForeignScalar(scalarBigint) + ); + s.mul(oneHalf); +} class Main extends Circuit { @circuitMain static main() { - main0(); + simpleMain(); } } @@ -239,11 +247,13 @@ console.log('compiling'); let kp = await Main.generateKeypair(); let cs = kp.constraintSystem(); -console.log(JSON.stringify(cs.filter((g) => g.type !== 'Zero'))); +// console.log(JSON.stringify(cs.filter((g) => g.type !== 'Zero'))); +console.log('# rows', cs.length); console.log('proving'); let proof0 = await Main.prove([], [], kp); +console.log('verifying'); let ok = await Main.verify([], kp.verificationKey(), proof0); console.log('verifies?', ok); @@ -252,7 +262,7 @@ let Program = ZkProgram({ test: { privateInputs: [], method() { - main0(); + simpleMain(); }, }, }, @@ -264,6 +274,7 @@ await Program.compile(); console.log('proving'); let proof = await Program.test(); +console.log('verifying'); ok = await Program.verify(proof); console.log('verifies?', ok); From f2f9bb100fdbbd82f17bd91abf161d5a8d747a34 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 28 Sep 2023 13:46:46 -0300 Subject: [PATCH 0125/1215] feat: add mina submodule to o1js --- .gitmodules | 3 +++ src/mina | 1 + 2 files changed, 4 insertions(+) create mode 160000 src/mina diff --git a/.gitmodules b/.gitmodules index b343059624..b8be721ce9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "src/snarkyjs-bindings"] path = src/bindings url = https://github.com/o1-labs/snarkyjs-bindings.git +[submodule "src/mina"] + path = src/mina + url = git@github.com:MinaProtocol/mina.git diff --git a/src/mina b/src/mina new file mode 160000 index 0000000000..396ae46c0f --- /dev/null +++ b/src/mina @@ -0,0 +1 @@ +Subproject commit 396ae46c0f301f697c91771bc6780571a7656a45 From b4771393907b4d917224693a495e343b005f26ce Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 28 Sep 2023 13:48:08 -0300 Subject: [PATCH 0126/1215] feat: add ocaml build directory to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 80c4e49312..8f62404233 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ tests/test-artifacts/ profiling.md o1js-reference *.tmp.js +_build/ From fc5966fc477fa53348974ba9218316c896ee9add Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 28 Sep 2023 13:48:36 -0300 Subject: [PATCH 0127/1215] feat: add dune-project file to build bindings from o1js root directory --- dune-project | 1 + 1 file changed, 1 insertion(+) create mode 100644 dune-project diff --git a/dune-project b/dune-project new file mode 100644 index 0000000000..7b17fb2d30 --- /dev/null +++ b/dune-project @@ -0,0 +1 @@ +(lang dune 3.3) From 00d2bd4d77397ffe03ecfd9c80bb4b5058402796 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 5 Oct 2023 14:32:07 -0700 Subject: [PATCH 0128/1215] chore(bindings): update subproject commit --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ffaaf33cc6..26723fd698 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ffaaf33cc6101d960f527ac09321b5f7c910d669 +Subproject commit 26723fd6987db505a8c44cc41e01b17ac496c22f From 148439c4033b4c3ce41d81aa137b13349d4ecd93 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Sun, 8 Oct 2023 15:56:45 +0300 Subject: [PATCH 0129/1215] An API for interaction with the lightnet accounts manager. --- .github/actions/live-tests-shared/action.yml | 14 +- src/examples/zkapps/hello_world/run_live.ts | 36 +++-- src/index.ts | 83 +++++------ src/lib/fetch.ts | 140 ++++++++++++++++--- src/lib/mina.ts | 121 +++++++++------- 5 files changed, 260 insertions(+), 134 deletions(-) diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index 6021811c29..8af8367d8b 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -1,11 +1,11 @@ -name: 'Shared steps for live testing jobs' -description: 'Shared steps for live testing jobs' +name: "Shared steps for live testing jobs" +description: "Shared steps for live testing jobs" inputs: mina-branch-name: - description: 'Mina branch name in use by service container' + description: "Mina branch name in use by service container" required: true runs: - using: 'composite' + using: "composite" steps: - name: Wait for Mina network readiness uses: o1-labs/wait-for-mina-network-action@v1 @@ -16,11 +16,11 @@ runs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: '20' + node-version: "20" - name: Build o1js and execute tests env: - TEST_TYPE: 'Live integration tests' - USE_LOCAL_NETWORK: 'true' + TEST_TYPE: "Live integration tests" + USE_CUSTOM_LOCAL_NETWORK: "true" run: | git submodule update --init --recursive npm ci diff --git a/src/examples/zkapps/hello_world/run_live.ts b/src/examples/zkapps/hello_world/run_live.ts index 4f7a9eb4a4..cac9594fd2 100644 --- a/src/examples/zkapps/hello_world/run_live.ts +++ b/src/examples/zkapps/hello_world/run_live.ts @@ -1,28 +1,35 @@ // Live integration test against real Mina network. -import { AccountUpdate, Field, Mina, PrivateKey, fetchAccount } from 'o1js'; +import { + AccountUpdate, + Field, + Mina, + PrivateKey, + acquireKeyPair, + fetchAccount, + releaseKeyPair, +} from 'o1js'; import { HelloWorld, adminPrivateKey } from './hello_world.js'; -const useLocalNetwork = process.env.USE_LOCAL_NETWORK === 'true'; -const senderKey = useLocalNetwork - ? PrivateKey.fromBase58( - 'EKEnVLUhYHDJvgmgQu5SzaV8MWKNfhAXYSkLBRk5KEfudWZRbs4P' - ) - : PrivateKey.random(); -const sender = senderKey.toPublicKey(); +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; const zkAppKey = PrivateKey.random(); const zkAppAddress = zkAppKey.toPublicKey(); const transactionFee = 100_000_000; // Network configuration -const network = Mina.Network( - useLocalNetwork +const network = Mina.Network({ + mina: useCustomLocalNetwork ? 'http://localhost:8080/graphql' - : 'https://proxy.berkeley.minaexplorer.com/graphql' -); + : 'https://proxy.berkeley.minaexplorer.com/graphql', + accountsManager: 'http://localhost:8181', +}); Mina.setActiveInstance(network); // Fee payer setup -if (!useLocalNetwork) { +const senderKey = useCustomLocalNetwork + ? (await acquireKeyPair(true)).privateKey + : PrivateKey.random(); +const sender = senderKey.toPublicKey(); +if (!useCustomLocalNetwork) { console.log(`Funding the fee payer account.`); await Mina.faucet(sender); } @@ -91,3 +98,6 @@ try { ); } console.log('Success!'); + +// Tear down +await releaseKeyPair(senderKey.toPublicKey().toBase58()); diff --git a/src/index.ts b/src/index.ts index 0813d3da4d..a19e641d7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,85 +1,88 @@ -export type { ProvablePure } from './snarky.js'; -export { Ledger } from './snarky.js'; -export { Field, Bool, Group, Scalar } from './lib/core.js'; -export { Poseidon, TokenSymbol } from './lib/hash.js'; -export * from './lib/signature.js'; -export type { - ProvableExtended, - FlexibleProvable, - FlexibleProvablePure, - InferProvable, -} from './lib/circuit_value.js'; +export { Types } from './bindings/mina-transaction/types.js'; +export { Circuit, Keypair, circuitMain, public_ } from './lib/circuit.js'; export { CircuitValue, - prop, + Struct, arrayProp, matrixProp, + prop, provable, provablePure, - Struct, } from './lib/circuit_value.js'; +export type { + FlexibleProvable, + FlexibleProvablePure, + InferProvable, + ProvableExtended, +} from './lib/circuit_value.js'; +export { Bool, Field, Group, Scalar } from './lib/core.js'; +export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Int64, Sign, UInt32, UInt64 } from './lib/int.js'; export { Provable } from './lib/provable.js'; -export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; -export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; -export { Types } from './bindings/mina-transaction/types.js'; +export * from './lib/signature.js'; +export { Ledger } from './snarky.js'; +export type { ProvablePure } from './snarky.js'; export * as Mina from './lib/mina.js'; -export type { DeployArgs } from './lib/zkapp.js'; +export { State, declareState, state } from './lib/state.js'; export { - SmartContract, - method, - declareMethods, Account, - VerificationKey, Reducer, + SmartContract, + VerificationKey, + declareMethods, + method, } from './lib/zkapp.js'; -export { state, State, declareState } from './lib/state.js'; +export type { DeployArgs } from './lib/zkapp.js'; -export type { JsonProof } from './lib/proof_system.js'; export { + Empty, Proof, SelfProof, - verify, - Empty, Undefined, Void, + verify, } from './lib/proof_system.js'; +export type { JsonProof } from './lib/proof_system.js'; export { - Token, - TokenId, AccountUpdate, Permissions, + Token, + TokenId, ZkappPublicInput, } from './lib/account_update.js'; -export type { TransactionStatus } from './lib/fetch.js'; +export * as Encoding from './bindings/lib/encoding.js'; +export * as Encryption from './lib/encryption.js'; export { + KeyPair, + acquireKeyPair, + addCachedAccount, + checkZkappTransaction, fetchAccount, + fetchEvents, fetchLastBlock, fetchTransactionStatus, - checkZkappTransaction, - fetchEvents, - addCachedAccount, + releaseKeyPair, + sendZkapp, + setArchiveGraphqlEndpoint, setGraphqlEndpoint, setGraphqlEndpoints, - setArchiveGraphqlEndpoint, - sendZkapp, } from './lib/fetch.js'; -export * as Encryption from './lib/encryption.js'; -export * as Encoding from './bindings/lib/encoding.js'; -export { Character, CircuitString } from './lib/string.js'; -export { MerkleTree, MerkleWitness } from './lib/merkle_tree.js'; +export type { TransactionStatus } from './lib/fetch.js'; export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; +export { MerkleTree, MerkleWitness } from './lib/merkle_tree.js'; +export { Character, CircuitString } from './lib/string.js'; export { Nullifier } from './lib/nullifier.js'; +export { Experimental }; // experimental APIs -import { ZkProgram } from './lib/proof_system.js'; -import { Callback } from './lib/zkapp.js'; import { createChildAccountUpdate } from './lib/account_update.js'; +import { ZkProgram } from './lib/proof_system.js'; import { memoizeWitness } from './lib/provable.js'; -export { Experimental }; +import { Callback } from './lib/zkapp.js'; const Experimental_ = { Callback, diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index c9b3d5cd66..1ecc1a2ac8 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,48 +1,52 @@ import 'isomorphic-fetch'; +import { Types } from '../bindings/mina-transaction/types.js'; +import { Actions, TokenId } from './account_update.js'; +import { EpochSeed, LedgerHash, StateHash } from './base58-encodings.js'; import { Field } from './core.js'; import { UInt32, UInt64 } from './int.js'; -import { Actions, TokenId } from './account_update.js'; -import { PublicKey } from './signature.js'; -import { NetworkValue } from './precondition.js'; -import { Types } from '../bindings/mina-transaction/types.js'; import { ActionStates } from './mina.js'; -import { LedgerHash, EpochSeed, StateHash } from './base58-encodings.js'; import { Account, - accountQuery, FetchedAccount, + PartialAccount, + accountQuery, fillPartialAccount, parseFetchedAccount, - PartialAccount, } from './mina/account.js'; +import { NetworkValue } from './precondition.js'; +import { PrivateKey, PublicKey } from './signature.js'; export { + EventActionFilterOptions, + KeyPair, + TransactionStatus, + acquireKeyPair, + addCachedAccount, + checkZkappTransaction, fetchAccount, + fetchActions, + fetchEvents, fetchLastBlock, - checkZkappTransaction, - parseFetchedAccount, - markAccountToBeFetched, - markNetworkToBeFetched, - markActionsToBeFetched, fetchMissingData, fetchTransactionStatus, - TransactionStatus, - EventActionFilterOptions, getCachedAccount, - getCachedNetwork, getCachedActions, - addCachedAccount, + getCachedNetwork, + markAccountToBeFetched, + markActionsToBeFetched, + markNetworkToBeFetched, networkConfig, + parseFetchedAccount, + releaseKeyPair, + removeJsonQuotes, + sendZkapp, + sendZkappQuery, + setAccountsManagerEndpoint, + setArchiveGraphqlEndpoint, + setArchiveGraphqlFallbackEndpoints, setGraphqlEndpoint, setGraphqlEndpoints, setMinaGraphqlFallbackEndpoints, - setArchiveGraphqlEndpoint, - setArchiveGraphqlFallbackEndpoints, - sendZkappQuery, - sendZkapp, - removeJsonQuotes, - fetchEvents, - fetchActions, }; type NetworkConfig = { @@ -50,6 +54,11 @@ type NetworkConfig = { minaFallbackEndpoints: string[]; archiveEndpoint: string; archiveFallbackEndpoints: string[]; + accountsManagerEndpoint: string; +}; +type KeyPair = { + publicKey: PublicKey; + privateKey: PrivateKey; }; let networkConfig = { @@ -57,6 +66,7 @@ let networkConfig = { minaFallbackEndpoints: [] as string[], archiveEndpoint: '', archiveFallbackEndpoints: [] as string[], + accountsManagerEndpoint: '', } satisfies NetworkConfig; function checkForValidUrl(url: string) { @@ -114,6 +124,20 @@ function setArchiveGraphqlFallbackEndpoints(graphqlEndpoints: string[]) { networkConfig.archiveFallbackEndpoints = graphqlEndpoints; } +/** + * Sets up the lightnet accounts manager endpoint to be used for accounts acquisition and releasing. + * + * @param endpoint Accounts manager endpoint. + */ +function setAccountsManagerEndpoint(endpoint: string) { + if (!checkForValidUrl(endpoint)) { + throw new Error( + `Invalid accounts manager endpoint: ${endpoint}. Please specify a valid URL.` + ); + } + networkConfig.accountsManagerEndpoint = endpoint; +} + /** * Gets account information on the specified publicKey by performing a GraphQL query * to the specified endpoint. This will call the 'GetAccountInfo' query which fetches @@ -993,6 +1017,76 @@ async function fetchActions( return actionsList; } +/** + * Gets random key pair (public and private keys) from accounts manager + * that operates with accounts configured in network's Genesis Ledger. + * + * If an error is returned by the specified endpoint, an error is thrown. Otherwise, + * the data is returned. + * + * @param isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) + * @param accountsManagerEndpoint Accounts manager endpoint to fetch from + * @returns Key pair + */ +async function acquireKeyPair( + isRegularAccount = true, + accountsManagerEndpoint = networkConfig.accountsManagerEndpoint +): Promise { + console.info( + `Attempting to acquire ${isRegularAccount ? 'non-' : ''}zkApp account.` + ); + const response = await fetch( + `${accountsManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return { + publicKey: PublicKey.fromBase58(data.pk), + privateKey: PrivateKey.fromBase58(data.sk), + }; + } + } + + throw new Error('Failed to acquire the key pair'); +} + +/** + * Releases previously acquired key pair from accounts manager by public key. + * + * @param publicKey Public key of previously acquired key pair to release + * @param accountsManagerEndpoint Accounts manager endpoint to fetch from + * @returns Void (since we don't really care about the success of this operation) + */ +async function releaseKeyPair( + publicKey: string, + accountsManagerEndpoint = networkConfig.accountsManagerEndpoint +): Promise { + const response = await fetch(`${accountsManagerEndpoint}/release-account`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pk: publicKey, + }), + }); + + if (response.ok) { + const data = await response.json(); + if (data) { + console.info(data.message); + } + } +} + function updateActionState(actions: string[][], actionState: Field) { let actionHash = Actions.fromJSON(actions).hash; return Actions.updateSequenceState(actionState, actionHash); diff --git a/src/lib/mina.ts b/src/lib/mina.ts index b1c9309581..4f5c39d007 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -1,69 +1,69 @@ +import { Types, TypesBigint } from '../bindings/mina-transaction/types.js'; +import { + transactionCommitments, + verifyAccountUpdateSignature, +} from '../mina-signer/src/sign-zkapp-command.js'; import { Ledger } from '../snarky.js'; -import { Field } from './core.js'; -import { UInt32, UInt64 } from './int.js'; -import { PrivateKey, PublicKey } from './signature.js'; import { - addMissingProofs, - addMissingSignatures, - FeePayerUnsigned, - ZkappCommand, AccountUpdate, - ZkappPublicInput, - TokenId, - CallForest, - Authorization, Actions, + Authorization, + CallForest, Events, + FeePayerUnsigned, + TokenId, + ZkappCommand, + ZkappPublicInput, + addMissingProofs, + addMissingSignatures, dummySignature, } from './account_update.js'; -import * as Fetch from './fetch.js'; -import { assertPreconditionInvariants, NetworkValue } from './precondition.js'; import { cloneCircuitValue, toConstant } from './circuit_value.js'; -import { Empty, Proof, verify } from './proof_system.js'; +import { Field } from './core.js'; +import { prettifyStacktrace } from './errors.js'; +import * as Fetch from './fetch.js'; import { Context } from './global-context.js'; -import { SmartContract } from './zkapp.js'; -import { invalidTransactionError } from './mina/errors.js'; -import { Types, TypesBigint } from '../bindings/mina-transaction/types.js'; +import { UInt32, UInt64 } from './int.js'; import { Account } from './mina/account.js'; import { TransactionCost, TransactionLimits } from './mina/constants.js'; -import { Provable } from './provable.js'; -import { prettifyStacktrace } from './errors.js'; +import { invalidTransactionError } from './mina/errors.js'; import { Ml } from './ml/conversion.js'; -import { - transactionCommitments, - verifyAccountUpdateSignature, -} from '../mina-signer/src/sign-zkapp-command.js'; +import { NetworkValue, assertPreconditionInvariants } from './precondition.js'; +import { Empty, Proof, verify } from './proof_system.js'; +import { Provable } from './provable.js'; +import { PrivateKey, PublicKey } from './signature.js'; +import { SmartContract } from './zkapp.js'; export { - createTransaction, + ActionStates, BerkeleyQANet, - Network, - LocalBlockchain, - currentTransaction, CurrentTransaction, + FeePayerSpec, + LocalBlockchain, + Network, Transaction, TransactionId, + accountCreationFee, activeInstance, - setActiveInstance, - transaction, - sender, + createTransaction, currentSlot, + currentTransaction, + faucet, + fetchActions, + fetchEvents, + // for internal testing only + filterGroups, getAccount, - hasAccount, + getActions, getBalance, getNetworkState, - accountCreationFee, + getProofsEnabled, + hasAccount, sendTransaction, - fetchEvents, - fetchActions, - getActions, - FeePayerSpec, - ActionStates, - faucet, + sender, + setActiveInstance, + transaction, waitForFunding, - getProofsEnabled, - // for internal testing only - filterGroups, }; interface TransactionId { isSuccess: boolean; @@ -677,22 +677,34 @@ LocalBlockchain satisfies (...args: any) => Mina; * Represents the Mina blockchain running on a real network */ function Network(graphqlEndpoint: string): Mina; -function Network(graphqlEndpoints: { +function Network(endpoints: { + mina: string | string[]; + accountsManager?: string; +}): Mina; +function Network(endpoints: { mina: string | string[]; archive: string | string[]; + accountsManager?: string; }): Mina; function Network( - input: { mina: string | string[]; archive: string | string[] } | string + input: + | { + mina: string | string[]; + archive?: string | string[]; + accountsManager?: string; + } + | string ): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); let minaGraphqlEndpoint: string; let archiveEndpoint: string; + let accountsManagerEndpoint: string; if (input && typeof input === 'string') { minaGraphqlEndpoint = input; Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); } else if (input && typeof input === 'object') { - if (!input.mina || !input.archive) + if (!input.mina) throw new Error( "Network: malformed input. Please provide an object with 'mina' and 'archive' endpoints." ); @@ -705,13 +717,20 @@ function Network( Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); } - if (Array.isArray(input.archive) && input.archive.length !== 0) { - archiveEndpoint = input.archive[0]; - Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); - Fetch.setArchiveGraphqlFallbackEndpoints(input.archive.slice(1)); - } else if (typeof input.archive === 'string') { - archiveEndpoint = input.archive; - Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + if (input.archive) { + if (Array.isArray(input.archive) && input.archive.length !== 0) { + archiveEndpoint = input.archive[0]; + Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + Fetch.setArchiveGraphqlFallbackEndpoints(input.archive.slice(1)); + } else if (typeof input.archive === 'string') { + archiveEndpoint = input.archive; + Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + } + } + + if (input.accountsManager && typeof input.accountsManager === 'string') { + accountsManagerEndpoint = input.accountsManager; + Fetch.setAccountsManagerEndpoint(accountsManagerEndpoint); } } else { throw new Error( From e7b2766ed55ad3f083509397f482009133ae9e19 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Sun, 8 Oct 2023 17:05:50 +0300 Subject: [PATCH 0130/1215] Imports/exports auto-formatting revert. --- .github/actions/live-tests-shared/action.yml | 14 ++-- src/index.ts | 86 ++++++++++---------- src/lib/fetch.ts | 54 ++++++------ src/lib/mina.ts | 82 +++++++++---------- 4 files changed, 118 insertions(+), 118 deletions(-) diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index 8af8367d8b..cc223da0e3 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -1,11 +1,11 @@ -name: "Shared steps for live testing jobs" -description: "Shared steps for live testing jobs" +name: 'Shared steps for live testing jobs' +description: 'Shared steps for live testing jobs' inputs: mina-branch-name: - description: "Mina branch name in use by service container" + description: 'Mina branch name in use by service container' required: true runs: - using: "composite" + using: 'composite' steps: - name: Wait for Mina network readiness uses: o1-labs/wait-for-mina-network-action@v1 @@ -16,11 +16,11 @@ runs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: "20" + node-version: '20' - name: Build o1js and execute tests env: - TEST_TYPE: "Live integration tests" - USE_CUSTOM_LOCAL_NETWORK: "true" + TEST_TYPE: 'Live integration tests' + USE_CUSTOM_LOCAL_NETWORK: 'true' run: | git submodule update --init --recursive npm ci diff --git a/src/index.ts b/src/index.ts index a19e641d7d..093abfc979 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,88 +1,88 @@ -export { Types } from './bindings/mina-transaction/types.js'; -export { Circuit, Keypair, circuitMain, public_ } from './lib/circuit.js'; +export type { ProvablePure } from './snarky.js'; +export { Ledger } from './snarky.js'; +export { Field, Bool, Group, Scalar } from './lib/core.js'; +export { Poseidon, TokenSymbol } from './lib/hash.js'; +export * from './lib/signature.js'; +export type { + ProvableExtended, + FlexibleProvable, + FlexibleProvablePure, + InferProvable, +} from './lib/circuit_value.js'; export { CircuitValue, - Struct, + prop, arrayProp, matrixProp, - prop, provable, provablePure, + Struct, } from './lib/circuit_value.js'; -export type { - FlexibleProvable, - FlexibleProvablePure, - InferProvable, - ProvableExtended, -} from './lib/circuit_value.js'; -export { Bool, Field, Group, Scalar } from './lib/core.js'; -export { Poseidon, TokenSymbol } from './lib/hash.js'; -export { Int64, Sign, UInt32, UInt64 } from './lib/int.js'; export { Provable } from './lib/provable.js'; -export * from './lib/signature.js'; -export { Ledger } from './snarky.js'; -export type { ProvablePure } from './snarky.js'; +export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; +export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; +export { Types } from './bindings/mina-transaction/types.js'; export * as Mina from './lib/mina.js'; -export { State, declareState, state } from './lib/state.js'; +export type { DeployArgs } from './lib/zkapp.js'; export { - Account, - Reducer, SmartContract, - VerificationKey, - declareMethods, method, + declareMethods, + Account, + VerificationKey, + Reducer, } from './lib/zkapp.js'; -export type { DeployArgs } from './lib/zkapp.js'; +export { state, State, declareState } from './lib/state.js'; +export type { JsonProof } from './lib/proof_system.js'; export { - Empty, Proof, SelfProof, + verify, + Empty, Undefined, Void, - verify, } from './lib/proof_system.js'; -export type { JsonProof } from './lib/proof_system.js'; export { - AccountUpdate, - Permissions, Token, TokenId, + AccountUpdate, + Permissions, ZkappPublicInput, } from './lib/account_update.js'; -export * as Encoding from './bindings/lib/encoding.js'; -export * as Encryption from './lib/encryption.js'; +export type { TransactionStatus } from './lib/fetch.js'; export { - KeyPair, - acquireKeyPair, - addCachedAccount, - checkZkappTransaction, fetchAccount, - fetchEvents, fetchLastBlock, fetchTransactionStatus, - releaseKeyPair, - sendZkapp, - setArchiveGraphqlEndpoint, + checkZkappTransaction, + fetchEvents, + addCachedAccount, setGraphqlEndpoint, setGraphqlEndpoints, + setArchiveGraphqlEndpoint, + sendZkapp, + KeyPair, + acquireKeyPair, + releaseKeyPair } from './lib/fetch.js'; -export type { TransactionStatus } from './lib/fetch.js'; -export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; -export { MerkleTree, MerkleWitness } from './lib/merkle_tree.js'; +export * as Encryption from './lib/encryption.js'; +export * as Encoding from './bindings/lib/encoding.js'; export { Character, CircuitString } from './lib/string.js'; +export { MerkleTree, MerkleWitness } from './lib/merkle_tree.js'; +export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; export { Nullifier } from './lib/nullifier.js'; -export { Experimental }; // experimental APIs -import { createChildAccountUpdate } from './lib/account_update.js'; import { ZkProgram } from './lib/proof_system.js'; -import { memoizeWitness } from './lib/provable.js'; import { Callback } from './lib/zkapp.js'; +import { createChildAccountUpdate } from './lib/account_update.js'; +import { memoizeWitness } from './lib/provable.js'; +export { Experimental }; const Experimental_ = { Callback, diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 1ecc1a2ac8..d045ce6570 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,52 +1,52 @@ import 'isomorphic-fetch'; -import { Types } from '../bindings/mina-transaction/types.js'; -import { Actions, TokenId } from './account_update.js'; -import { EpochSeed, LedgerHash, StateHash } from './base58-encodings.js'; import { Field } from './core.js'; import { UInt32, UInt64 } from './int.js'; +import { Actions, TokenId } from './account_update.js'; +import { PublicKey, PrivateKey } from './signature.js'; +import { NetworkValue } from './precondition.js'; +import { Types } from '../bindings/mina-transaction/types.js'; import { ActionStates } from './mina.js'; +import { LedgerHash, EpochSeed, StateHash } from './base58-encodings.js'; import { Account, - FetchedAccount, - PartialAccount, accountQuery, + FetchedAccount, fillPartialAccount, parseFetchedAccount, + PartialAccount, } from './mina/account.js'; -import { NetworkValue } from './precondition.js'; -import { PrivateKey, PublicKey } from './signature.js'; export { - EventActionFilterOptions, - KeyPair, - TransactionStatus, - acquireKeyPair, - addCachedAccount, - checkZkappTransaction, fetchAccount, - fetchActions, - fetchEvents, fetchLastBlock, + checkZkappTransaction, + parseFetchedAccount, + markAccountToBeFetched, + markNetworkToBeFetched, + markActionsToBeFetched, fetchMissingData, fetchTransactionStatus, + TransactionStatus, + EventActionFilterOptions, getCachedAccount, - getCachedActions, getCachedNetwork, - markAccountToBeFetched, - markActionsToBeFetched, - markNetworkToBeFetched, + getCachedActions, + addCachedAccount, networkConfig, - parseFetchedAccount, - releaseKeyPair, - removeJsonQuotes, - sendZkapp, - sendZkappQuery, - setAccountsManagerEndpoint, - setArchiveGraphqlEndpoint, - setArchiveGraphqlFallbackEndpoints, setGraphqlEndpoint, setGraphqlEndpoints, setMinaGraphqlFallbackEndpoints, + setArchiveGraphqlEndpoint, + setArchiveGraphqlFallbackEndpoints, + setAccountsManagerEndpoint, + sendZkappQuery, + sendZkapp, + removeJsonQuotes, + fetchEvents, + fetchActions, + KeyPair, + acquireKeyPair, + releaseKeyPair }; type NetworkConfig = { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 4f5c39d007..6a1827e7f7 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -1,69 +1,69 @@ -import { Types, TypesBigint } from '../bindings/mina-transaction/types.js'; -import { - transactionCommitments, - verifyAccountUpdateSignature, -} from '../mina-signer/src/sign-zkapp-command.js'; import { Ledger } from '../snarky.js'; +import { Field } from './core.js'; +import { UInt32, UInt64 } from './int.js'; +import { PrivateKey, PublicKey } from './signature.js'; import { - AccountUpdate, - Actions, - Authorization, - CallForest, - Events, + addMissingProofs, + addMissingSignatures, FeePayerUnsigned, - TokenId, ZkappCommand, + AccountUpdate, ZkappPublicInput, - addMissingProofs, - addMissingSignatures, + TokenId, + CallForest, + Authorization, + Actions, + Events, dummySignature, } from './account_update.js'; -import { cloneCircuitValue, toConstant } from './circuit_value.js'; -import { Field } from './core.js'; -import { prettifyStacktrace } from './errors.js'; import * as Fetch from './fetch.js'; +import { assertPreconditionInvariants, NetworkValue } from './precondition.js'; +import { cloneCircuitValue, toConstant } from './circuit_value.js'; +import { Empty, Proof, verify } from './proof_system.js'; import { Context } from './global-context.js'; -import { UInt32, UInt64 } from './int.js'; +import { SmartContract } from './zkapp.js'; +import { invalidTransactionError } from './mina/errors.js'; +import { Types, TypesBigint } from '../bindings/mina-transaction/types.js'; import { Account } from './mina/account.js'; import { TransactionCost, TransactionLimits } from './mina/constants.js'; -import { invalidTransactionError } from './mina/errors.js'; -import { Ml } from './ml/conversion.js'; -import { NetworkValue, assertPreconditionInvariants } from './precondition.js'; -import { Empty, Proof, verify } from './proof_system.js'; import { Provable } from './provable.js'; -import { PrivateKey, PublicKey } from './signature.js'; -import { SmartContract } from './zkapp.js'; +import { prettifyStacktrace } from './errors.js'; +import { Ml } from './ml/conversion.js'; +import { + transactionCommitments, + verifyAccountUpdateSignature, +} from '../mina-signer/src/sign-zkapp-command.js'; export { - ActionStates, + createTransaction, BerkeleyQANet, - CurrentTransaction, - FeePayerSpec, - LocalBlockchain, Network, + LocalBlockchain, + currentTransaction, + CurrentTransaction, Transaction, TransactionId, - accountCreationFee, activeInstance, - createTransaction, + setActiveInstance, + transaction, + sender, currentSlot, - currentTransaction, - faucet, - fetchActions, - fetchEvents, - // for internal testing only - filterGroups, getAccount, - getActions, + hasAccount, getBalance, getNetworkState, - getProofsEnabled, - hasAccount, + accountCreationFee, sendTransaction, - sender, - setActiveInstance, - transaction, + fetchEvents, + fetchActions, + getActions, + FeePayerSpec, + ActionStates, + faucet, waitForFunding, + getProofsEnabled, + // for internal testing only + filterGroups, }; interface TransactionId { isSuccess: boolean; From da6d39e9f92b7171f1c075d57208d3ab80e73747 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 9 Oct 2023 10:38:35 +0300 Subject: [PATCH 0131/1215] Addressing review comments (separate namespace on will go next). --- src/examples/zkapps/hello_world/run_live.ts | 9 +- src/index.ts | 1 - src/lib/fetch.ts | 94 ++++++++++++--------- src/lib/mina.ts | 25 +++--- 4 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/examples/zkapps/hello_world/run_live.ts b/src/examples/zkapps/hello_world/run_live.ts index cac9594fd2..eeace4805d 100644 --- a/src/examples/zkapps/hello_world/run_live.ts +++ b/src/examples/zkapps/hello_world/run_live.ts @@ -20,13 +20,13 @@ const network = Mina.Network({ mina: useCustomLocalNetwork ? 'http://localhost:8080/graphql' : 'https://proxy.berkeley.minaexplorer.com/graphql', - accountsManager: 'http://localhost:8181', + lightnetAccountManager: 'http://localhost:8181', }); Mina.setActiveInstance(network); // Fee payer setup const senderKey = useCustomLocalNetwork - ? (await acquireKeyPair(true)).privateKey + ? (await acquireKeyPair()).privateKey : PrivateKey.random(); const sender = senderKey.toPublicKey(); if (!useCustomLocalNetwork) { @@ -100,4 +100,7 @@ try { console.log('Success!'); // Tear down -await releaseKeyPair(senderKey.toPublicKey().toBase58()); +const keyPairReleaseMessage = await releaseKeyPair({ + publicKey: sender.toBase58(), +}); +if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); diff --git a/src/index.ts b/src/index.ts index 093abfc979..a4dae15e8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,7 +65,6 @@ export { setGraphqlEndpoints, setArchiveGraphqlEndpoint, sendZkapp, - KeyPair, acquireKeyPair, releaseKeyPair } from './lib/fetch.js'; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index d045ce6570..75fd8dfb47 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -38,13 +38,12 @@ export { setMinaGraphqlFallbackEndpoints, setArchiveGraphqlEndpoint, setArchiveGraphqlFallbackEndpoints, - setAccountsManagerEndpoint, + setLightnetAccountManagerEndpoint, sendZkappQuery, sendZkapp, removeJsonQuotes, fetchEvents, fetchActions, - KeyPair, acquireKeyPair, releaseKeyPair }; @@ -54,11 +53,7 @@ type NetworkConfig = { minaFallbackEndpoints: string[]; archiveEndpoint: string; archiveFallbackEndpoints: string[]; - accountsManagerEndpoint: string; -}; -type KeyPair = { - publicKey: PublicKey; - privateKey: PrivateKey; + lightnetAccountManagerEndpoint: string; }; let networkConfig = { @@ -66,7 +61,7 @@ let networkConfig = { minaFallbackEndpoints: [] as string[], archiveEndpoint: '', archiveFallbackEndpoints: [] as string[], - accountsManagerEndpoint: '', + lightnetAccountManagerEndpoint: '', } satisfies NetworkConfig; function checkForValidUrl(url: string) { @@ -125,17 +120,17 @@ function setArchiveGraphqlFallbackEndpoints(graphqlEndpoints: string[]) { } /** - * Sets up the lightnet accounts manager endpoint to be used for accounts acquisition and releasing. + * Sets up the lightnet account manager endpoint to be used for accounts acquisition and releasing. * - * @param endpoint Accounts manager endpoint. + * @param endpoint Account manager endpoint. */ -function setAccountsManagerEndpoint(endpoint: string) { +function setLightnetAccountManagerEndpoint(endpoint: string) { if (!checkForValidUrl(endpoint)) { throw new Error( - `Invalid accounts manager endpoint: ${endpoint}. Please specify a valid URL.` + `Invalid account manager endpoint: ${endpoint}. Please specify a valid URL.` ); } - networkConfig.accountsManagerEndpoint = endpoint; + networkConfig.lightnetAccountManagerEndpoint = endpoint; } /** @@ -1018,25 +1013,31 @@ async function fetchActions( } /** - * Gets random key pair (public and private keys) from accounts manager - * that operates with accounts configured in network's Genesis Ledger. + * Gets random key pair (public and private keys) from account manager + * that operates with accounts configured in target network Genesis Ledger. * * If an error is returned by the specified endpoint, an error is thrown. Otherwise, * the data is returned. * - * @param isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) - * @param accountsManagerEndpoint Accounts manager endpoint to fetch from + * @param options.isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Key pair */ async function acquireKeyPair( - isRegularAccount = true, - accountsManagerEndpoint = networkConfig.accountsManagerEndpoint -): Promise { - console.info( - `Attempting to acquire ${isRegularAccount ? 'non-' : ''}zkApp account.` - ); + options: { + isRegularAccount?: boolean; + lightnetAccountManagerEndpoint?: string; + } = {} +): Promise<{ + publicKey: PublicKey; + privateKey: PrivateKey; +}> { + const { + isRegularAccount = true, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; const response = await fetch( - `${accountsManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, + `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, { method: 'GET', headers: { @@ -1059,32 +1060,41 @@ async function acquireKeyPair( } /** - * Releases previously acquired key pair from accounts manager by public key. + * Releases previously acquired key pair by public key. * - * @param publicKey Public key of previously acquired key pair to release - * @param accountsManagerEndpoint Accounts manager endpoint to fetch from - * @returns Void (since we don't really care about the success of this operation) + * @param options.publicKey Public key of previously acquired key pair to release + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Response message from the account manager as string or null if the request failed */ -async function releaseKeyPair( - publicKey: string, - accountsManagerEndpoint = networkConfig.accountsManagerEndpoint -): Promise { - const response = await fetch(`${accountsManagerEndpoint}/release-account`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - pk: publicKey, - }), - }); +async function releaseKeyPair(options: { + publicKey: string; + lightnetAccountManagerEndpoint?: string; +}): Promise { + const { + publicKey, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/release-account`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pk: publicKey, + }), + } + ); if (response.ok) { const data = await response.json(); if (data) { - console.info(data.message); + return data.message as string; } } + + return null; } function updateActionState(actions: string[][], actionState: Field) { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 6a1827e7f7..28d6e3e7bf 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -679,26 +679,22 @@ LocalBlockchain satisfies (...args: any) => Mina; function Network(graphqlEndpoint: string): Mina; function Network(endpoints: { mina: string | string[]; - accountsManager?: string; -}): Mina; -function Network(endpoints: { - mina: string | string[]; - archive: string | string[]; - accountsManager?: string; + archive?: string | string[]; + lightnetAccountManager?: string; }): Mina; function Network( input: | { mina: string | string[]; archive?: string | string[]; - accountsManager?: string; + lightnetAccountManager?: string; } | string ): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); let minaGraphqlEndpoint: string; let archiveEndpoint: string; - let accountsManagerEndpoint: string; + let lightnetAccountManagerEndpoint: string; if (input && typeof input === 'string') { minaGraphqlEndpoint = input; @@ -706,7 +702,7 @@ function Network( } else if (input && typeof input === 'object') { if (!input.mina) throw new Error( - "Network: malformed input. Please provide an object with 'mina' and 'archive' endpoints." + "Network: malformed input. Please provide an object with 'mina' endpoint." ); if (Array.isArray(input.mina) && input.mina.length !== 0) { minaGraphqlEndpoint = input.mina[0]; @@ -717,7 +713,7 @@ function Network( Fetch.setGraphqlEndpoint(minaGraphqlEndpoint); } - if (input.archive) { + if (input.archive !== undefined) { if (Array.isArray(input.archive) && input.archive.length !== 0) { archiveEndpoint = input.archive[0]; Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); @@ -728,9 +724,12 @@ function Network( } } - if (input.accountsManager && typeof input.accountsManager === 'string') { - accountsManagerEndpoint = input.accountsManager; - Fetch.setAccountsManagerEndpoint(accountsManagerEndpoint); + if ( + input.lightnetAccountManager !== undefined && + typeof input.lightnetAccountManager === 'string' + ) { + lightnetAccountManagerEndpoint = input.lightnetAccountManager; + Fetch.setLightnetAccountManagerEndpoint(lightnetAccountManagerEndpoint); } } else { throw new Error( From 0d4bbd6dfec6d6af6f5b6f56174edb4e16e3e029 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 9 Oct 2023 10:57:47 +0300 Subject: [PATCH 0132/1215] Export only Lightnet namespace. --- src/examples/zkapps/hello_world/run_live.ts | 7 +- src/index.ts | 3 +- src/lib/fetch.ts | 155 ++++++++++---------- 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/src/examples/zkapps/hello_world/run_live.ts b/src/examples/zkapps/hello_world/run_live.ts index eeace4805d..c54d323a8a 100644 --- a/src/examples/zkapps/hello_world/run_live.ts +++ b/src/examples/zkapps/hello_world/run_live.ts @@ -2,11 +2,10 @@ import { AccountUpdate, Field, + Lightnet, Mina, PrivateKey, - acquireKeyPair, fetchAccount, - releaseKeyPair, } from 'o1js'; import { HelloWorld, adminPrivateKey } from './hello_world.js'; @@ -26,7 +25,7 @@ Mina.setActiveInstance(network); // Fee payer setup const senderKey = useCustomLocalNetwork - ? (await acquireKeyPair()).privateKey + ? (await Lightnet.acquireKeyPair()).privateKey : PrivateKey.random(); const sender = senderKey.toPublicKey(); if (!useCustomLocalNetwork) { @@ -100,7 +99,7 @@ try { console.log('Success!'); // Tear down -const keyPairReleaseMessage = await releaseKeyPair({ +const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ publicKey: sender.toBase58(), }); if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); diff --git a/src/index.ts b/src/index.ts index a4dae15e8e..b65a42c11f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,8 +65,7 @@ export { setGraphqlEndpoints, setArchiveGraphqlEndpoint, sendZkapp, - acquireKeyPair, - releaseKeyPair + Lightnet } from './lib/fetch.js'; export * as Encryption from './lib/encryption.js'; export * as Encoding from './bindings/lib/encoding.js'; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 75fd8dfb47..8723d5ba1e 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -44,8 +44,7 @@ export { removeJsonQuotes, fetchEvents, fetchActions, - acquireKeyPair, - releaseKeyPair + Lightnet }; type NetworkConfig = { @@ -1012,89 +1011,91 @@ async function fetchActions( return actionsList; } -/** - * Gets random key pair (public and private keys) from account manager - * that operates with accounts configured in target network Genesis Ledger. - * - * If an error is returned by the specified endpoint, an error is thrown. Otherwise, - * the data is returned. - * - * @param options.isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) - * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from - * @returns Key pair - */ -async function acquireKeyPair( - options: { - isRegularAccount?: boolean; - lightnetAccountManagerEndpoint?: string; - } = {} -): Promise<{ - publicKey: PublicKey; - privateKey: PrivateKey; -}> { - const { - isRegularAccount = true, - lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, - } = options; - const response = await fetch( - `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - } - ); +namespace Lightnet { + /** + * Gets random key pair (public and private keys) from account manager + * that operates with accounts configured in target network Genesis Ledger. + * + * If an error is returned by the specified endpoint, an error is thrown. Otherwise, + * the data is returned. + * + * @param options.isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Key pair + */ + export async function acquireKeyPair( + options: { + isRegularAccount?: boolean; + lightnetAccountManagerEndpoint?: string; + } = {} + ): Promise<{ + publicKey: PublicKey; + privateKey: PrivateKey; + }> { + const { + isRegularAccount = true, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); - if (response.ok) { - const data = await response.json(); - if (data) { - return { - publicKey: PublicKey.fromBase58(data.pk), - privateKey: PrivateKey.fromBase58(data.sk), - }; + if (response.ok) { + const data = await response.json(); + if (data) { + return { + publicKey: PublicKey.fromBase58(data.pk), + privateKey: PrivateKey.fromBase58(data.sk), + }; + } } + + throw new Error('Failed to acquire the key pair'); } - throw new Error('Failed to acquire the key pair'); -} + /** + * Releases previously acquired key pair by public key. + * + * @param options.publicKey Public key of previously acquired key pair to release + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Response message from the account manager as string or null if the request failed + */ + export async function releaseKeyPair(options: { + publicKey: string; + lightnetAccountManagerEndpoint?: string; + }): Promise { + const { + publicKey, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/release-account`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pk: publicKey, + }), + } + ); -/** - * Releases previously acquired key pair by public key. - * - * @param options.publicKey Public key of previously acquired key pair to release - * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from - * @returns Response message from the account manager as string or null if the request failed - */ -async function releaseKeyPair(options: { - publicKey: string; - lightnetAccountManagerEndpoint?: string; -}): Promise { - const { - publicKey, - lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, - } = options; - const response = await fetch( - `${lightnetAccountManagerEndpoint}/release-account`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - pk: publicKey, - }), + if (response.ok) { + const data = await response.json(); + if (data) { + return data.message as string; + } } - ); - if (response.ok) { - const data = await response.json(); - if (data) { - return data.message as string; - } + return null; } - - return null; } function updateActionState(actions: string[][], actionState: Field) { From 268589755f570dbb3508ba38749906791e091b6b Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Tue, 10 Oct 2023 09:56:35 +0300 Subject: [PATCH 0133/1215] Updated changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55c81885b..eb07fb4ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/045faa7...HEAD) +### Added + +- New API available under the `Leghtnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes From 8caf7e19fb17ad7d4e327baf5d55fd7a21898090 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 10:06:08 +0200 Subject: [PATCH 0134/1215] use the TS `asserts` keyword --- src/lib/errors.ts | 5 ++++- src/lib/hash.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 80307985c8..b7e65c0338 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -274,6 +274,9 @@ function Bug(message: string) { /** * Make an assertion. When failing, this will communicate to users it's not their fault but indicates an internal bug. */ -function assert(condition: boolean, message = 'Failed assertion.') { +function assert( + condition: boolean, + message = 'Failed assertion.' +): asserts condition { if (!condition) throw Bug(message); } diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 9abf35b662..684b7632ce 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -64,7 +64,7 @@ const Poseidon = { if (isConstant(input)) { let result = PoseidonBigint.hashToGroup(toBigints(input)); assert(result !== undefined, 'hashToGroup works on all inputs'); - let { x, y } = result!; + let { x, y } = result; return { x: Field(x), y: { x0: Field(y.x0), x1: Field(y.x1) }, From a20e6da3636d1180eefc83feedcb51e573196cb1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 10:42:08 +0200 Subject: [PATCH 0135/1215] don't introduce an extra var when sealing a constant --- src/examples/regression_test.json | 110 +++++++++++++++--------------- src/lib/field.ts | 4 +- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index c8916cc0c4..cf510442dc 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -1,139 +1,139 @@ { "Voting_": { - "digest": "2f0ca1dca22d7af925d31d23f3d4e37686ec1755a9f069063ae3b6300f6b3920", + "digest": "11174ce0c67e7de9bde81e278b8d4e008bf85e39cafb4dab7ff9f544b357f602", "methods": { "voterRegistration": { - "rows": 1260, - "digest": "5d6fa3b1f0f781fba8894da098827088" + "rows": 1259, + "digest": "5f435c8335d3384d7775abc7884bb2b3" }, "candidateRegistration": { - "rows": 1260, - "digest": "abf93b4d926a8743bf622da4ee0f88b2" + "rows": 1259, + "digest": "0c23f82dfb0f104183fff054c109d64a" }, "approveRegistrations": { "rows": 1146, - "digest": "197ce95e1895f940278034d20ab24274" + "digest": "ec68c1d8ab22e779ccbd2659dd6b46cd" }, "vote": { "rows": 1674, - "digest": "a0dd0be93734dc39d52fc7d60d0f0bab" + "digest": "293138c59fab55ec431829040903c6e5" }, "countVotes": { - "rows": 5797, - "digest": "9c5503e03dcbb6b04c907eb1da43e26e" + "rows": 5796, + "digest": "775f327a408b3f3d7bae4e3ff18aeb54" } }, "verificationKey": { - "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAMlwrr5wrHUaGeTWFlLisFAPw2kdtXHUaWFMr8rlLZAH95JF1AU+hzhCIMmrPGuqtWSZ3PevCs61j8Mg1hZc7yYc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EWsSEg+BHXhfDdeVIH8kIDSRHLhfHm4ekJzyDdkhSrpx2vUjEr3DsCxEebGoDTQn3asUnHGhnGWBYwF2POo4QuBvqS2B9pOUgfgpcmcvpUe0yTZ3WrOkHl1RwJL07ktygdm/SxKUslsfL3Ds6RrXEDr65EJ2ArVceibKJPp8cvhwYMqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", - "hash": "11565671297372915311669581079391063231397733208021234592662736001743584177724" + "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAEgTiVyzydYxNTKRpau/O5JaThaBCqePJzujSXLd5uIguUQkKMjVpfnHKOeoOtlMY8PYYFASPZjP4K1Y1XpE5DIc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EW4LNvtxxoO0iaOURB2IB7VADucBYxQ1+cS7VRPhyNExTcCZyXuFiIswyGL+IRgGq/6zB9gjg5WKdBNBSS3rnZGqr2Upc/Gpxs0TNpiqK38JVU3KUC+oE48VL9+7WGD0s1WjA3MwCbNOBAqR4+X9ARtA68p+Z7odHcYjqBEPqnYT8MqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", + "hash": "6540205725785398466714331980040484379552614946521100582595062590036343420759" } }, "Membership_": { - "digest": "fb87402442d6984de28d7bab62deb87ab8a7f46d5c2d59da4980ae6927dd1ec", + "digest": "1557502514ffa55b1f014453a6f984e55d053d0518c8e50cbd11099ac5748f4a", "methods": { "addEntry": { - "rows": 1354, - "digest": "bdb5dd653383ec699e25e418135aeec0" + "rows": 1353, + "digest": "fa32e32384aef8ce6a7d019142149d95" }, "isMember": { "rows": 470, - "digest": "f1b63907b48c40cd01b1d34166fa86eb" + "digest": "c344c6f2575a61066da9dd4365c5e137" }, "publish": { - "rows": 695, - "digest": "c6be510154c0e624a41756ad349df9de" + "rows": 694, + "digest": "c7f77b05b8ec477338849af8dcb34a11" } }, "verificationKey": { - "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAK8auJnAo+Ayoz9QWJYOyPCcdSOOu06auILgZc5OvCIQ3JDLaRLCOVxcDqSyBjhkiGid2eXAIb9unaj/ZtIH0zm2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxckU6470FP1JqRV4SOd0KOhihaeAUFeAXycuok7/nt3dhZW8juNm39lwVhQ6wyTRXGZT9+y5BBglIqA2LehBYoNJGTPe56ukPLNlOisxnYe/Bk8CxsWtY1GhKPPY0qGRZIhsrgZdgfkxPT19xI0t2Th5Uz9IdzapUDwAGgSy0E0zDD6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", - "hash": "11513384784058506063139870141763328684986961462198948028100954667309086537868" + "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAHHgQqvSF2AEzSEy6kDop6fnFtVTxzp0MgW0M9X0uVcRTRJTkcVZSz1JzihGEjzkEZnZW6tVr6CEkmzXh/t3DSq2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxckEIHNbrLurLOLNw0ULooGQl29Wa9ypCF03j+8G8vvyT0tSFBtP5wtikpblHFWehI15JoPRar3i832pKBljjTROZ/oj/hTZ69X7CDmyJ6yGWV5oymxsbSqnCnNuPvgvXsawzI68Dl5J3p5BouuDS5xmhVnNGHh1sXunbTNSFe82CP6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", + "hash": "6153483930236453200463602918744126128783798859183980971182639907212857992418" } }, "HelloWorld": { - "digest": "5efddf140c592bb0bfd51d9c77252bcc0c0fab1fbd7fbaf85bd3bb051a96596", + "digest": "20cadc6f44ecd546700e9fac15aa2740d4357d46ee03c2627c36be49b02e8227", "methods": { "update": { - "rows": 2352, - "digest": "217224955830a2c0db65a6dcc91cae29" + "rows": 2351, + "digest": "f5b77fd12fee155fd3a40946dd453962" } }, "verificationKey": { - "data": "AAAxHIvaXF+vRj2/+pyAfE6U29d1K5GmGbhiKR9lTC6LJ2o1ygGxXERl1oQh6DBxf/hDUD0HOeg/JajCp3V6b5wytil2mfx8v2DB5RuNQ7VxJWkha0TSnJJsOl0FxhjldBbOY3tUZzZxHpPhHOKHz/ZAXRYFIsf2x+7boXC0iPurETHN7j5IevHIgf2fSW8WgHZYn83hpVI33LBdN1pIbUc7oWAUQVmmgp04jRqTCYK1oNg+Y9DeIuT4EVbp/yN7eS7Ay8ahic2sSAZvtn08MdRyk/jm2cLlJbeAAad6Xyz/H9l7JrkbVwDMMPxvHVHs27tNoJCzIlrRzB7pg3ju9aQOu4h3thDr+WSgFQWKvcRPeL7f3TFjIr8WZ2457RgMcTwXwORKbqJCcyKVNOE+FlNwVkOKER+WIpC0OlgGuayPFwQQkbb91jaRlJvahfwkbF2+AJmDnavmNpop9T+/Xak1adXIrsRPeOjC+qIKxIbGimoMOoYzYlevKA80LnJ7HC0IxR+yNLvoSYxDDPNRD+OCCxk5lM2h8IDUiCNWH4FZNJ+doiigKjyZlu/xZ7jHcX7qibu/32KFTX85DPSkQM8dALqTYvt53Y2qtBATwNK97/SJ/NcMvhcXE4RGfofcGhoEK8I7PkcmU5pc3qaZ6a1jailHXKV47zJNSF/4HyjpQAQKR89XcqLS/NP7lwCEej/L8q8R7sKGMCXmgFYluWH4JBSPDgvMxScfjFS33oBNb7po8cLnAORzohXoYTSgztklD0mKn6EegLbkLtwwr9ObsLz3m7fp/3wkNWFRkY5xzSZN1VybbQbmpyQNCpxd/kdDsvlszqlowkyC8HnKbhnvE0Mrz3ZIk4vSs/UGBSXAoESFCFCPcTq11TCOhE5rumMJErv5LusDHJgrBtQUMibLU9A1YbF7SPDAR2QZd0yx3wbCC54QZ2t+mZ4s6RQndfRpndXoIZJgari62jHRccBnGpRmURHG20jukwW6RYDDED7OlvEzEhFlsXyViehlSn4EwBn97R43eHZQvzawB0Xo8qVfOHstjgWKF0vSwenrWxwq8p1y9IQeEWVpLG8IU6dzZfX2/X71b5I74QwxlrLRM0exIlapLarEGI7ZVvg5jAqXDlXxVS3HRo/skxgt2LYm8wLIKLHX0ClznArLVLXkSX18cSoSsVMG3QCSsmH1Oh8xOGUbSHzawovjubcH7qWjIZoghZJ16QB1c0ryiAfHB48OHhs2p/JZWz8Dp7kfcPkeg2Of2NbupJlNVMLIH4IGWaPAscBRkZ+F4oLqOhJ5as7fAzzU8PQdeZi0YgssGDJVmNEHP61I16KZNcxQqR0EUVwhyMmYmpVjvtfhHi/6I3mfPS+FDxTuf4yaqVF0xg2V3ep/WYnnKPJIegxoTFY8pChjyow3PMfhAP5HOnXjHQ2Va9BFo4mfEQXvRzPmIRRVmlVsP8zA+xuHylyiww/Lercce7cq0YA5PtYS3ge9IDYwXckBUXb5ikD3alrrv5mvMu6itB7ix2f8lbiF9Fkmc4Bk2ycIWXJDCuBN+2sTFqzUeoT6xY8XWaOcnDvqOgSm/CCSv38umiOE2jEpsKYxhRc6W70UJkrzd3hr2DiSF1I2B+krpUVK1GeOdCLC5sl7YPzk+pF8183uI9wse6UTlqIiroKqsggzLBy/IjAfxS0BxFy5zywXqp+NogFkoTEJmR5MaqOkPfap+OsD1lGScY6+X4WW/HqCWrmA3ZTqDGngQMTGXLCtl6IS/cQpihS1NRbNqOtKTaCB9COQu0oz6RivBlywuaj3MKUdmbQ2gVDj+SGQItCNaXawyPSBjB9VT+68SoJVySQsYPCuEZCb0V/40n/a7RAbyrnNjP+2HwD7p27Pl1RSzqq35xiPdnycD1UeEPLpx/ON65mYCkn+KLQZmkqPio+vA2KmJngWTx+ol4rVFimGm76VT0xCFDsu2K0YX0yoLNH4u2XfmT9NR8gGfkVRCnnNjlbgHQmEwC75+GmEJ5DjD3d+s6IXTQ60MHvxbTHHlnfmPbgKn2SAI0uVoewKC9GyK6dSaboLw3C48jl0E2kyc+7umhCk3kEeWmt//GSjRNhoq+B+mynXiOtgFs/Am2v1TBjSb+6tcijsf5tFJmeGxlCjJnTdNWBkSHpMoo6OFkkpA6/FBAUHLSM7Yv8oYyd0GtwF5cCwQ6aRTbl9oG/mUn5Q92OnDMQcUjpgEho0Dcp2OqZyyxqQSPrbIIZZQrS2HkxBgjcfcSTuSHo7ONqlRjLUpO5yS95VLGXBLLHuCiIMGT+DW6DoJRtRIS+JieVWBoX0YsWgYInXrVlWUv6gDng5AyVFkUIFwZk7/3mVAgvXO83ArVKA4S747jT60w5bgV4Jy55slDM=", - "hash": "16374078687847242751733960472737775252305759960836159362409166419576402399087" + "data": "AAAxHIvaXF+vRj2/+pyAfE6U29d1K5GmGbhiKR9lTC6LJ2o1ygGxXERl1oQh6DBxf/hDUD0HOeg/JajCp3V6b5wytil2mfx8v2DB5RuNQ7VxJWkha0TSnJJsOl0FxhjldBbOY3tUZzZxHpPhHOKHz/ZAXRYFIsf2x+7boXC0iPurETHN7j5IevHIgf2fSW8WgHZYn83hpVI33LBdN1pIbUc7oWAUQVmmgp04jRqTCYK1oNg+Y9DeIuT4EVbp/yN7eS7Ay8ahic2sSAZvtn08MdRyk/jm2cLlJbeAAad6Xyz/H9l7JrkbVwDMMPxvHVHs27tNoJCzIlrRzB7pg3ju9aQOu4h3thDr+WSgFQWKvcRPeL7f3TFjIr8WZ2457RgMcTwXwORKbqJCcyKVNOE+FlNwVkOKER+WIpC0OlgGuayPFwQQkbb91jaRlJvahfwkbF2+AJmDnavmNpop9T+/Xak1adXIrsRPeOjC+qIKxIbGimoMOoYzYlevKA80LnJ7HC0IxR+yNLvoSYxDDPNRD+OCCxk5lM2h8IDUiCNWH4FZNJ+doiigKjyZlu/xZ7jHcX7qibu/32KFTX85DPSkQM8dADbWQUmeiyX6c8BmLNrtM9m7dAj8BktFxGV9DpdhbakQltUbxbGrb3EcZ+43YFE/yWa3/WAQL81kbrXD0yjFthEKR89XcqLS/NP7lwCEej/L8q8R7sKGMCXmgFYluWH4JBSPDgvMxScfjFS33oBNb7po8cLnAORzohXoYTSgztklD0mKn6EegLbkLtwwr9ObsLz3m7fp/3wkNWFRkY5xzSZN1VybbQbmpyQNCpxd/kdDsvlszqlowkyC8HnKbhnvE0Mrz3ZIk4vSs/UGBSXAoESFCFCPcTq11TCOhE5rumMJErv5LusDHJgrBtQUMibLU9A1YbF7SPDAR2QZd0yx3wbCC54QZ2t+mZ4s6RQndfRpndXoIZJgari62jHRccBnGpRmURHG20jukwW6RYDDED7OlvEzEhFlsXyViehlSn4Evb44z+VGilheD0D6v1paoVTv2A4m5ZVGEQOeoCQELABdoFrIZRrd4+glnXPz8Gy4nOI/rmGgnPa9fSK0N1zMKEexIlapLarEGI7ZVvg5jAqXDlXxVS3HRo/skxgt2LYm8wLIKLHX0ClznArLVLXkSX18cSoSsVMG3QCSsmH1Oh8xOGUbSHzawovjubcH7qWjIZoghZJ16QB1c0ryiAfHB48OHhs2p/JZWz8Dp7kfcPkeg2Of2NbupJlNVMLIH4IGWaPAscBRkZ+F4oLqOhJ5as7fAzzU8PQdeZi0YgssGDJVmNEHP61I16KZNcxQqR0EUVwhyMmYmpVjvtfhHi/6I3mfPS+FDxTuf4yaqVF0xg2V3ep/WYnnKPJIegxoTFY8pChjyow3PMfhAP5HOnXjHQ2Va9BFo4mfEQXvRzPmIRRVmlVsP8zA+xuHylyiww/Lercce7cq0YA5PtYS3ge9IDYwXckBUXb5ikD3alrrv5mvMu6itB7ix2f8lbiF9Fkmc4Bk2ycIWXJDCuBN+2sTFqzUeoT6xY8XWaOcnDvqOgSm/CCSv38umiOE2jEpsKYxhRc6W70UJkrzd3hr2DiSF1I2B+krpUVK1GeOdCLC5sl7YPzk+pF8183uI9wse6UTlqIiroKqsggzLBy/IjAfxS0BxFy5zywXqp+NogFkoTEJmR5MaqOkPfap+OsD1lGScY6+X4WW/HqCWrmA3ZTqDGngQMTGXLCtl6IS/cQpihS1NRbNqOtKTaCB9COQu0oz6RivBlywuaj3MKUdmbQ2gVDj+SGQItCNaXawyPSBjB9VT+68SoJVySQsYPCuEZCb0V/40n/a7RAbyrnNjP+2HwD7p27Pl1RSzqq35xiPdnycD1UeEPLpx/ON65mYCkn+KLQZmkqPio+vA2KmJngWTx+ol4rVFimGm76VT0xCFDsu2K0YX0yoLNH4u2XfmT9NR8gGfkVRCnnNjlbgHQmEwC75+GmEJ5DjD3d+s6IXTQ60MHvxbTHHlnfmPbgKn2SAI0uVoewKC9GyK6dSaboLw3C48jl0E2kyc+7umhCk3kEeWmt//GSjRNhoq+B+mynXiOtgFs/Am2v1TBjSb+6tcijsf5tFJmeGxlCjJnTdNWBkSHpMoo6OFkkpA6/FBAUHLSM7Yv8oYyd0GtwF5cCwQ6aRTbl9oG/mUn5Q92OnDMQcUjpgEho0Dcp2OqZyyxqQSPrbIIZZQrS2HkxBgjcfcSTuSHo7ONqlRjLUpO5yS95VLGXBLLHuCiIMGT+DW6DoJRtRIS+JieVWBoX0YsWgYInXrVlWUv6gDng5AyVFkUIFwZk7/3mVAgvXO83ArVKA4S747jT60w5bgV4Jy55slDM=", + "hash": "28560680247074990771744165492810964987846406526367865642032954725768850073454" } }, "TokenContract": { - "digest": "10ccc8e9c4a5788084a73993036c15ea25a5b2643ad06b76614de7e2910b2cc", + "digest": "346c5ce0416c2479d962f0868825b4bcbf68f5beac5e7a93632013a6c57d1be8", "methods": { "init": { - "rows": 656, - "digest": "b6debf22e710ad6bfb67177517ed66ea" + "rows": 655, + "digest": "3941ac88f0b92eec098dfcf46faa4e60" }, "init2": { - "rows": 653, - "digest": "b5ac64e93cd25de68a96f1a7b8cee9aa" + "rows": 652, + "digest": "1ebb84a10bafd30accfd3e8046d2e20d" }, "deployZkapp": { - "rows": 703, - "digest": "9b45cc038648bab83ea14ed64b08aa79" + "rows": 702, + "digest": "e5ac2667a79f44f1e7a65b12d8ac006c" }, "approveUpdate": { - "rows": 1929, - "digest": "5ededd9323d9347dc5c91250c08b5206" + "rows": 1928, + "digest": "f8bd1807567dc405f841283227dfb158" }, "approveAny": { "rows": 1928, - "digest": "b34bc95ca48bfc7fc09f8d1b84215389" + "digest": "240aada76b79de1ca67ecbe455621378" }, "approveUpdateAndSend": { - "rows": 2322, - "digest": "7359c73a15841a7c958b97cc86da58e6" + "rows": 2321, + "digest": "b1cff49cdc3cc751f802b4b5aee53383" }, "transferToAddress": { - "rows": 1045, - "digest": "79bdb6579a9f42dc4ec2d3e9ceedbe1c" + "rows": 1044, + "digest": "212879ca2441ccc20f5e58940833cf35" }, "transferToUpdate": { - "rows": 2327, - "digest": "f7297e43a8082e4677855bca9a206a88" + "rows": 2326, + "digest": "a7241cbc2946a3c468e600003a5d9a16" }, "getBalance": { - "rows": 687, - "digest": "738dbad00d454b34d06dd87a346aff11" + "rows": 686, + "digest": "44a90b65d1d7ee553811759b115d12cc" } }, "verificationKey": { - "data": "AAAVRdJJF0DehjdPSA0kYGZTkzSfoEaHqDprP5lbtp+BLeGqblAzBabKYB+hRBo7ijFWFnIHV4LwvOlCtrAhNtk/Ae0EY5Tlufvf2snnstKNDXVgcRc/zNAaS5iW43PYqQnEYsaesXs/y5DeeEaFxwdyujsHSK/UaltNLsCc34RKG71O/TGRVVX/eYb8saPPV9W5YjPHLQdhqcHRU6Qq7hMEI1ejTXMokQcurz7jtYU/P56OYekAREejgrEV38U82BbgJigOmh5NhgGTBSAhJ35c9XCsJldUMd5xZiua9cWxGOHm0r7TkcCrV9CEPm5sT7sP7IYQ5dnSdPoi/sy7moUPRitxw7iGvewRVXro6rIemmbxNSzKXWprnl6ewrB2HTppMUEZRp7zYkFIaNDHpvdw4dvjX6K/i527/jwX0JL4BideRc+z3FNhj1VBSHhhvMzwFW6aUwSmWC4UCuwDBokkkBtUE0YYH8kwFnMoWWAlDzHekrxaVmxWRS0lvkr8IDlsR5kyq8SMXFLgKJjoFr6HZWE4tkO/abEgrsK1A3c9F5r/G2yUdMQZu8JMwxUY5qw7D09IPsUQ63c5/CJpea8PAHcbRTYdRCpbscSmIstbX8jWBrg9LufKaJLfVQsGuyIvSHmj9IbGTLpbPr6uz/+gTuce5EOv1uHkF3g8HxyomAtU3betWNXGJbS4dC4hTNfWM956bK+fwkIlwhM3BC+wOai+M0+y9/y/RSI8qJkSU3MqOF9+nrifKRyNQ3KILqIyR7LjE0/Z/4NzH7eF3uZTBlqfLdf8WhXdwvOPoP1dCx1shF6g4Hh9V4myikRZBtkix1cO5FLUNLNAFw+glg1PB1eA+4ATFuFcfMjxDpDjxqCFCyuQ5TaLuNfYMA7fiO0vB6yqtWgSmCOlD/MQqAhHYRMq4PXk3TUQSle8XBZ67T0+gENjIJleTRgZFG6PgIEwHXcsKIvfFAPklTlnY+5sNVw8yBisVaFgw36DrHWNavWvsZM5HwD0h1Wk0hkavjEI5BbtUDG+pn3lbowHxfFJ/wf5VCLNQfn/di8uGbG0egJyahU9gHfw4MWCMHi8MI5Ofj/cqCekGDjDfnfymsSzPrFW8S2prrtw7qFiIZITFLHFe+jZN2a5iTEoIhyUOvYP+zJbfD1mrqoY7g0Iizhh610wdBy6TiGgfJpcDf3kUxuav/WbabGDMJhbugO4TNu1/i5omH8pbsjGGHQXk1UYPoP1SnMVPZ9RXPoWHJn/kePU9QqGxETHF4T7b2Ov7CcZDLuz147VCknmGiziHzbmYJleu4tzSlFsxHPkp2d9JiDUbO7X66Dh/+84gc5KWpMnEIAF9gITi3cXUglZTjWaASaXcpgHXXGZHZJcrG2VfPNjgTKJ1+CbvyXlvuhvX+0E2oaPB+BoP0i2iTXQHPNhOY/Gg2h6uKvE5fSSiYC7Rws2TGF1aEM54wX3Ti1qA1cAiNG5y8yk1YMGCk3TPqs9MRp0qjgjJbbvFlbgPkkqz5o6c7g8gfhIa4VEJyyI2joqJeIc7vMZFWhquSFHNs0TZKvKLiSAsyNDrpWZb/1PHxziswKvisk296AJi7hmlM1pKx6S4LlbT2OKLXbgq5HUKfe8QhxG4aOsPSSiVGwvnCrIPdSxLq77M27UWXnXHC8mmJmOsGUFj+bdX/u6AgrBhw/w74dDbuNEpC80PbJTuglF/TeDryYsFWCrBnF/WPstgzy3zDDTZ3DXHVYVxOEvErIynlQEY9Cv9QSxRI3dA+hLtob/L78ZeJSU4Al+Qv0QGZTOxQORosVshOP2eFQ1VMKGWOpCVvyi8QE4fa+gOgYT0JRm4rkQBZ5WDlYGkamD3euC92Kd7Z39G89h/AqeFACahkAW1a78SzLW69mZ+CDLfKp/xQsi2TWgJqGh7QNOEtMnn/2owLzLWd071mvUtT0484Eqx6hUqLJMH70p8oUjQIMsh0mvp1BWSU8XC6z+UZIpVm2CERrV8BMLmTLOgTNJlEIJQR7zzpJCDFNNOI+Y2ZtdcuU8XHgcsQhQ3PgCACFAWN3rO+goXoTWdYR/LcqszKzPnMArmPIHWkRM6Mkm13OsHXCVudUbqQjC/pNQZH1VW+RMXnre1vQVb3fnCy5h28Dce3Q2WzjBSZFhe3iADZpo7gWHM/sqe+Mbnbn8A+RRWVNbtjss9376jN73zV4xPH3un3VjTxrzCluqR8MbH8t7mhPBqV5CslmSIbDNruVXtwCf4VS1nssw63PfLzeOSvzhTTsg82rna/+TKl1RIwhD8VFnCDq/Rk8fdy/+K5qP6GcSTbh6J8ERx4jOOukL9TUCpJkhvo/3ED8GOewmWAwzL8avXuf9AFvhwH3ENp5v4IIGBljuDJ77vckGmTI=", - "hash": "19471120960548999816351394077166332230185600464930675317083534065611063009411" + "data": "AAAVRdJJF0DehjdPSA0kYGZTkzSfoEaHqDprP5lbtp+BLeGqblAzBabKYB+hRBo7ijFWFnIHV4LwvOlCtrAhNtk/Ae0EY5Tlufvf2snnstKNDXVgcRc/zNAaS5iW43PYqQnEYsaesXs/y5DeeEaFxwdyujsHSK/UaltNLsCc34RKG71O/TGRVVX/eYb8saPPV9W5YjPHLQdhqcHRU6Qq7hMEI1ejTXMokQcurz7jtYU/P56OYekAREejgrEV38U82BbgJigOmh5NhgGTBSAhJ35c9XCsJldUMd5xZiua9cWxGOHm0r7TkcCrV9CEPm5sT7sP7IYQ5dnSdPoi/sy7moUPRitxw7iGvewRVXro6rIemmbxNSzKXWprnl6ewrB2HTppMUEZRp7zYkFIaNDHpvdw4dvjX6K/i527/jwX0JL4BideRc+z3FNhj1VBSHhhvMzwFW6aUwSmWC4UCuwDBokkkBtUE0YYH8kwFnMoWWAlDzHekrxaVmxWRS0lvkr8IDlsR5kyq8SMXFLgKJjoFr6HZWE4tkO/abEgrsK1A3c9F5r/G2yUdMQZu8JMwxUY5qw7D09IPsUQ63c5/CJpea8PAHbUlzRl2KhAhm58JzY0th81wwK0uXhv2e0aXMoEpM0YViAu+c/32zmBe6xl97uBNmNWwlWOLEpHakq46OzONidU3betWNXGJbS4dC4hTNfWM956bK+fwkIlwhM3BC+wOai+M0+y9/y/RSI8qJkSU3MqOF9+nrifKRyNQ3KILqIyR7LjE0/Z/4NzH7eF3uZTBlqfLdf8WhXdwvOPoP1dCx1shF6g4Hh9V4myikRZBtkix1cO5FLUNLNAFw+glg1PB1eA+4ATFuFcfMjxDpDjxqCFCyuQ5TaLuNfYMA7fiO0vB6yqtWgSmCOlD/MQqAhHYRMq4PXk3TUQSle8XBZ67T0+gENjIJleTRgZFG6PgIEwHXcsKIvfFAPklTlnY+5sNVw8yBisVaFgw36DrHWNavWvsZM5HwD0h1Wk0hkavjEIz9nTxQU+nsZsR+70ALZ69HljR0fUjNU7qpVmpYBlRiFxA/BWf8qie2wfhSfy6Q1v5Ee4+3vN/mYuS3uF47LkM1dRTanQ73mLIz80yky+lCNkLWHmZtyWjtMsDFNgupc+yc+FvFNjJM/ea6u3PROtSyU3rAlmchkKvxO4qfrd0iqav/WbabGDMJhbugO4TNu1/i5omH8pbsjGGHQXk1UYPoP1SnMVPZ9RXPoWHJn/kePU9QqGxETHF4T7b2Ov7CcZDLuz147VCknmGiziHzbmYJleu4tzSlFsxHPkp2d9JiDUbO7X66Dh/+84gc5KWpMnEIAF9gITi3cXUglZTjWaASaXcpgHXXGZHZJcrG2VfPNjgTKJ1+CbvyXlvuhvX+0E2oaPB+BoP0i2iTXQHPNhOY/Gg2h6uKvE5fSSiYC7Rws2TGF1aEM54wX3Ti1qA1cAiNG5y8yk1YMGCk3TPqs9MRp0qjgjJbbvFlbgPkkqz5o6c7g8gfhIa4VEJyyI2joqJeIc7vMZFWhquSFHNs0TZKvKLiSAsyNDrpWZb/1PHxziswKvisk296AJi7hmlM1pKx6S4LlbT2OKLXbgq5HUKfe8QhxG4aOsPSSiVGwvnCrIPdSxLq77M27UWXnXHC8mmJmOsGUFj+bdX/u6AgrBhw/w74dDbuNEpC80PbJTuglF/TeDryYsFWCrBnF/WPstgzy3zDDTZ3DXHVYVxOEvErIynlQEY9Cv9QSxRI3dA+hLtob/L78ZeJSU4Al+Qv0QGZTOxQORosVshOP2eFQ1VMKGWOpCVvyi8QE4fa+gOgYT0JRm4rkQBZ5WDlYGkamD3euC92Kd7Z39G89h/AqeFACahkAW1a78SzLW69mZ+CDLfKp/xQsi2TWgJqGh7QNOEtMnn/2owLzLWd071mvUtT0484Eqx6hUqLJMH70p8oUjQIMsh0mvp1BWSU8XC6z+UZIpVm2CERrV8BMLmTLOgTNJlEIJQR7zzpJCDFNNOI+Y2ZtdcuU8XHgcsQhQ3PgCACFAWN3rO+goXoTWdYR/LcqszKzPnMArmPIHWkRM6Mkm13OsHXCVudUbqQjC/pNQZH1VW+RMXnre1vQVb3fnCy5h28Dce3Q2WzjBSZFhe3iADZpo7gWHM/sqe+Mbnbn8A+RRWVNbtjss9376jN73zV4xPH3un3VjTxrzCluqR8MbH8t7mhPBqV5CslmSIbDNruVXtwCf4VS1nssw63PfLzeOSvzhTTsg82rna/+TKl1RIwhD8VFnCDq/Rk8fdy/+K5qP6GcSTbh6J8ERx4jOOukL9TUCpJkhvo/3ED8GOewmWAwzL8avXuf9AFvhwH3ENp5v4IIGBljuDJ77vckGmTI=", + "hash": "13796172868423455932596117465273580383420853883879480382066094121613342871544" } }, "Dex": { - "digest": "2039439604f1b4a88563bd34178bb380c0d998cc662b8fe9b9ea1164eefe70c7", + "digest": "1f017f88141fc7145e1b7007dd5b04766aa71ab3940f4dfa7bd2536e2beb883c", "methods": { "supplyLiquidityBase": { - "rows": 3752, - "digest": "8d00a233c53340e6ef8898bb40ad919c" + "rows": 3751, + "digest": "457c0524f3a26ee275c0c92434fbb03f" }, "swapX": { "rows": 1986, - "digest": "23a2ddbc46117681d58c03a130cfee3f" + "digest": "e1c79fee9c8f94815daa5d6fee7c5181" }, "swapY": { "rows": 1986, - "digest": "c218b1e81ac390ba81678a5be89bc7a7" + "digest": "4cf07c1491e7fc167edcf3a26d636f3d" }, "burnLiquidity": { - "rows": 719, - "digest": "63d13fc14775e43e4f6dd6245b74d516" + "rows": 718, + "digest": "99fb50066d2aa2f3f7afe801009cb503" }, "transfer": { - "rows": 1045, - "digest": "dd6f98961085734ff5ec578746db3a53" + "rows": 1044, + "digest": "7c188ff9cf8e7db108e2d24f8e097622" } }, "verificationKey": { - "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZACz8aW+oEDHXv3riERCsWdAt/zvOg0mMRDp1/RqJ/18vkTKQH2mfv2wQlLNUma0senYjXxHGjRexXfLGK88bpCVK3k+3L+ZskBbfKiZXd+gbKIy6+Hv2EhkBjtG9h6OrEggQwRgP4mWuoJ91bSnCFiMLJyH5dV4yry6WsGsfiUIFJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AM0ziaXap3vDs8CDbm/+mAPE51oDFs5zs57N0ue8N/OhsUv6w9D2axBwF8udR96c+MZkLi29Fu/ZC3Op4qCZK+PUpnwM1uzqAGDEbkGfUh1UR/PColxru25K5GS10vC0oFC+TdXVnVj3kTdztGDJk0udozRbpvb+S6A1YwO6+OOhH59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", - "hash": "14012766312159048636010037107657830276597168503405582242210403680501924916774" + "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAOLg+O5JsHz6EFkkWxwdCSmy1K1LFaS6A78wbTtc9uIslLAntKxTApVE2lxPzH+TwHBFMkSweXxdP3dGxtecxxquRuMqw2lvtzmPG1qQ6HZFGYlidPZ9TDFQtxO55utoJOUVC2GPM8TFnx+/OL+lwMSEZTfkTgyfSkl4jTgjbzgdJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AM84dcwR1Ur7O0YSVR5TXXJhMigCPYsF0/fmLijOWNAga8rtMJvF0oZ02aoQv4KpGu9yq72CsoXSpWqu/6C+GSP+XlJ3aLn+OxrDIDZd39WP6XdSug2oTN2hVm2VkmfMss+mGfxTQzrenM6JX7A2pqemjUlKqM9yLg8swF85hdGxn59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", + "hash": "24296734038411361102326027139139575382494468159357248593236667562317362889930" } }, "Group Primitive": { diff --git a/src/lib/field.ts b/src/lib/field.ts index 8b7bf25df1..f5ae1a103c 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1036,9 +1036,7 @@ class Field { * @return A {@link Field} element that is equal to the result of AST that was previously on this {@link Field} element. */ seal() { - // TODO: this is just commented for constraint equivalence with the old version - // uncomment to sometimes save constraints - // if (this.isConstant()) return this; + if (this.isConstant()) return this; let x = Snarky.field.seal(this.value); return new Field(x); } From 3ec439c98fe7a164a1056c40047080cc06ff5c19 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 10:45:34 +0200 Subject: [PATCH 0136/1215] improve Field.equals() --- src/examples/regression_test.json | 58 +++++++++++++++---------------- src/lib/field.ts | 8 ++--- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index cf510442dc..3223a71d5a 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -1,22 +1,22 @@ { "Voting_": { - "digest": "11174ce0c67e7de9bde81e278b8d4e008bf85e39cafb4dab7ff9f544b357f602", + "digest": "3f56ff09ceba13daf64b20cd48419395a04aa0007cac20e6e9c5f9106f251c3a", "methods": { "voterRegistration": { - "rows": 1259, - "digest": "5f435c8335d3384d7775abc7884bb2b3" + "rows": 1258, + "digest": "5572b0d59feea6b199f3f45af7498d92" }, "candidateRegistration": { - "rows": 1259, - "digest": "0c23f82dfb0f104183fff054c109d64a" + "rows": 1258, + "digest": "07c8451f1c1ea4e9653548d411d5728c" }, "approveRegistrations": { "rows": 1146, "digest": "ec68c1d8ab22e779ccbd2659dd6b46cd" }, "vote": { - "rows": 1674, - "digest": "293138c59fab55ec431829040903c6e5" + "rows": 1672, + "digest": "fa5671190ca2cc46084cae922a62288e" }, "countVotes": { "rows": 5796, @@ -24,20 +24,20 @@ } }, "verificationKey": { - "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAEgTiVyzydYxNTKRpau/O5JaThaBCqePJzujSXLd5uIguUQkKMjVpfnHKOeoOtlMY8PYYFASPZjP4K1Y1XpE5DIc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EW4LNvtxxoO0iaOURB2IB7VADucBYxQ1+cS7VRPhyNExTcCZyXuFiIswyGL+IRgGq/6zB9gjg5WKdBNBSS3rnZGqr2Upc/Gpxs0TNpiqK38JVU3KUC+oE48VL9+7WGD0s1WjA3MwCbNOBAqR4+X9ARtA68p+Z7odHcYjqBEPqnYT8MqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", - "hash": "6540205725785398466714331980040484379552614946521100582595062590036343420759" + "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAEgTiVyzydYxNTKRpau/O5JaThaBCqePJzujSXLd5uIguUQkKMjVpfnHKOeoOtlMY8PYYFASPZjP4K1Y1XpE5DIc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EWVKjlhM+1lrOQC7OfL98Sy0lD9j349LjxKcpiLTM7xxR/fSS4Yv9QXnEZxDigYQO7N+8yMm6PfgqtNLa4gTlCOq1tWaRtaZtq24x+SyOo5P8EXWYuvsV/qMMPNmhoTq85lDI+iwlA1xDTYyFHBdUe/zfoe5Znk7Ej3dQt+wVKtRgMqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", + "hash": "1740450553572902301764143810281331039416167348454304895395553400061364101079" } }, "Membership_": { - "digest": "1557502514ffa55b1f014453a6f984e55d053d0518c8e50cbd11099ac5748f4a", + "digest": "255745fb9365ff4f970b96ed630c01c9c8f63e21744f83fbe833396731d096e2", "methods": { "addEntry": { "rows": 1353, "digest": "fa32e32384aef8ce6a7d019142149d95" }, "isMember": { - "rows": 470, - "digest": "c344c6f2575a61066da9dd4365c5e137" + "rows": 469, + "digest": "16dae12385ed7e5aca9161030a426335" }, "publish": { "rows": 694, @@ -45,8 +45,8 @@ } }, "verificationKey": { - "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAHHgQqvSF2AEzSEy6kDop6fnFtVTxzp0MgW0M9X0uVcRTRJTkcVZSz1JzihGEjzkEZnZW6tVr6CEkmzXh/t3DSq2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxckEIHNbrLurLOLNw0ULooGQl29Wa9ypCF03j+8G8vvyT0tSFBtP5wtikpblHFWehI15JoPRar3i832pKBljjTROZ/oj/hTZ69X7CDmyJ6yGWV5oymxsbSqnCnNuPvgvXsawzI68Dl5J3p5BouuDS5xmhVnNGHh1sXunbTNSFe82CP6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", - "hash": "6153483930236453200463602918744126128783798859183980971182639907212857992418" + "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAHHgQqvSF2AEzSEy6kDop6fnFtVTxzp0MgW0M9X0uVcRTRJTkcVZSz1JzihGEjzkEZnZW6tVr6CEkmzXh/t3DSq2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxcktsWwr3mRVBRM4iPa87OEKZOxq0wWPrGcTnmqV/ihFAcp38VS2KUNwsiWjprCq1MFDCf1dT4c1U6/mdLP6AI/AJi7REoCfvJfwxSZYr2obhnskD1VjqelMdksHemFbsQDczNhNcSg1TTD5ZsuG71wj9rSJPEisRCRRd733MLARwv6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", + "hash": "16610506589527352533348678289715227768202510979537802187565243095524972136674" } }, "HelloWorld": { @@ -108,11 +108,11 @@ } }, "Dex": { - "digest": "1f017f88141fc7145e1b7007dd5b04766aa71ab3940f4dfa7bd2536e2beb883c", + "digest": "14f902411526156cdf7de9a822a3f6467f7608a135504038993cbc8efeaf720a", "methods": { "supplyLiquidityBase": { - "rows": 3751, - "digest": "457c0524f3a26ee275c0c92434fbb03f" + "rows": 3749, + "digest": "08830f49d9e8a4bf683db63c1c19bd28" }, "swapX": { "rows": 1986, @@ -132,32 +132,32 @@ } }, "verificationKey": { - "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAOLg+O5JsHz6EFkkWxwdCSmy1K1LFaS6A78wbTtc9uIslLAntKxTApVE2lxPzH+TwHBFMkSweXxdP3dGxtecxxquRuMqw2lvtzmPG1qQ6HZFGYlidPZ9TDFQtxO55utoJOUVC2GPM8TFnx+/OL+lwMSEZTfkTgyfSkl4jTgjbzgdJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AM84dcwR1Ur7O0YSVR5TXXJhMigCPYsF0/fmLijOWNAga8rtMJvF0oZ02aoQv4KpGu9yq72CsoXSpWqu/6C+GSP+XlJ3aLn+OxrDIDZd39WP6XdSug2oTN2hVm2VkmfMss+mGfxTQzrenM6JX7A2pqemjUlKqM9yLg8swF85hdGxn59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", - "hash": "24296734038411361102326027139139575382494468159357248593236667562317362889930" + "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAOLg+O5JsHz6EFkkWxwdCSmy1K1LFaS6A78wbTtc9uIslLAntKxTApVE2lxPzH+TwHBFMkSweXxdP3dGxtecxxpbbLKvz9Clh3WpX0ia/8PSErjEfdpClkDrgo8DG2MpEgFaBcgfyFNTEhXLnxCiGlwjJ+DdBAfnonMPIkkY6p0SJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AM84dcwR1Ur7O0YSVR5TXXJhMigCPYsF0/fmLijOWNAga8rtMJvF0oZ02aoQv4KpGu9yq72CsoXSpWqu/6C+GSP52zL9QV0VkohE1njGsSrC/EMtWuNxk6avge+WIxnbAbrFVGoWKdAN3uuZBKQW6ehhi1watI+S5lkpbpTnrK3R/59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", + "hash": "6361961148584909856756402479432671413765163664396823312454174383287651676472" } }, "Group Primitive": { "digest": "Group Primitive", "methods": { "add": { - "rows": 33, - "digest": "4e687eaee46e4c03860f46ceb2e50bd3" + "rows": 32, + "digest": "8754c691505a1d11559afac1361c58e4" }, "sub": { - "rows": 34, - "digest": "dc048f79ddfd96938d439b8960d42d57" + "rows": 33, + "digest": "f1bb469656221c45fa9eddaf1af8fbc0" }, "scale": { - "rows": 114, - "digest": "198a5c73fb635db3d7ca134c02687aba" + "rows": 113, + "digest": "b912611500f01c57177285f538438abc" }, "equals": { - "rows": 42, - "digest": "bca7af6eb9762989838d484c8e29a205" + "rows": 37, + "digest": "59cd8f24e1e0f3ba721f9c5380801335" }, "assertions": { - "rows": 20, - "digest": "d4525ed2ab7b897ab5d9e8309a2fdba2" + "rows": 19, + "digest": "7d87f453433117a306b19e50a5061443" } }, "verificationKey": { diff --git a/src/lib/field.ts b/src/lib/field.ts index f5ae1a103c..649dd0b443 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -520,7 +520,8 @@ class Field { * @return A {@link Field} element equivalent to the modular division of the two value. */ div(y: Field | bigint | number | string) { - // TODO this is the same as snarky-ml but could use 1 constraint instead of 2 + // this intentionally uses 2 constraints instead of 1 to avoid an unconstrained output when dividing 0/0 + // (in this version, division by 0 is strictly not allowed) return this.mul(Field.from(y).inv()); } @@ -633,10 +634,6 @@ class Field { */ equals(y: Field | bigint | number | string): Bool { // x == y is equivalent to x - y == 0 - // TODO: this is less efficient than possible for equivalence with snarky-ml - return this.sub(y).isZero(); - // more efficient code is commented below - /* // if one of the two is constant, we just need the two constraints in `isZero` if (this.isConstant() || isConstant(y)) { return this.sub(y).isZero(); @@ -647,7 +644,6 @@ class Field { ); Snarky.field.assertEqual(this.sub(y).value, xMinusY); return new Field(xMinusY).isZero(); - */ } // internal base method for all comparisons From 6d0bbad32e6456674e22127c319d2f0f68487081 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 10:47:52 +0200 Subject: [PATCH 0137/1215] we can now use Bool constructor for FIeldVars --- src/lib/field.ts | 9 +++------ src/lib/ml/conversion.ts | 6 +----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 649dd0b443..18e7a5efe5 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -616,7 +616,7 @@ class Field { // ^^^ these prove that b = Bool(x === 0): // if x = 0, the 2nd equation implies b = 1 // if x != 0, the 1st implies b = 0 - return Bool.Unsafe.ofField(new Field(b)); + return new Bool(b); } /** @@ -661,10 +661,7 @@ class Field { ); }); let [, less, lessOrEqual] = Snarky.field.compare(maxLength, this.value, y); - return { - less: Bool.Unsafe.ofField(new Field(less)), - lessOrEqual: Bool.Unsafe.ofField(new Field(lessOrEqual)), - }; + return { less: new Bool(less), lessOrEqual: new Bool(lessOrEqual) }; } /** @@ -960,7 +957,7 @@ class Field { return bits.map((b) => new Bool(b)); } let [, ...bits] = Snarky.field.toBits(length ?? Fp.sizeInBits, this.value); - return bits.map((b) => Bool.Unsafe.ofField(new Field(b))); + return bits.map((b) => new Bool(b)); } /** diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 646deac480..0ce74e727c 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -99,9 +99,5 @@ function fromPublicKeyVar(pk: PublicKey): MlPublicKeyVar { return MlTuple(pk.x.value, pk.isOdd.toField().value); } function toPublicKeyVar([, x, isOdd]: MlPublicKeyVar): PublicKey { - return PublicKey.from({ - x: Field(x), - // TODO - isOdd: Bool.Unsafe.ofField(Field(isOdd)), - }); + return PublicKey.from({ x: Field(x), isOdd: Bool(isOdd) }); } From c478cebfb777205a503c349835377f0c6f2639f7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 10:57:54 +0200 Subject: [PATCH 0138/1215] improve group addition --- src/examples/regression_test.json | 8 ++++---- src/lib/group.ts | 27 ++++++--------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 3223a71d5a..e492f2d163 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -140,12 +140,12 @@ "digest": "Group Primitive", "methods": { "add": { - "rows": 32, - "digest": "8754c691505a1d11559afac1361c58e4" + "rows": 30, + "digest": "8179f9497cc9b6624912033324c27b6d" }, "sub": { - "rows": 33, - "digest": "f1bb469656221c45fa9eddaf1af8fbc0" + "rows": 30, + "digest": "ddb709883792aa08b3bdfb69206a9f69" }, "scale": { "rows": 113, diff --git a/src/lib/group.ts b/src/lib/group.ts index 71f95fdae8..552a8263ad 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -35,10 +35,7 @@ class Group { * ``` */ static get zero() { - return new Group({ - x: 0, - y: 0, - }); + return new Group({ x: 0, y: 0 }); } /** @@ -187,27 +184,15 @@ class Group { // 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 thisIsZero = this.isZero(); - - let bothZero = gIsZero.and(thisIsZero); - - let onlyGisZero = gIsZero.and(thisIsZero.not()); - let onlyThisIsZero = thisIsZero.and(gIsZero.not()); - + let onlyThisIsZero = this.isZero().and(gIsZero.not()); let isNegation = inf; + let isNormalAddition = gIsZero.or(onlyThisIsZero).or(isNegation).not(); - let isNewElement = bothZero - .not() - .and(isNegation.not()) - .and(onlyThisIsZero.not()) - .and(onlyGisZero.not()); - - const zero_g = Group.zero; - + // 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( - [bothZero, onlyGisZero, onlyThisIsZero, isNegation, isNewElement], + [gIsZero, onlyThisIsZero, isNegation, isNormalAddition], Group, - [zero_g, this, g, zero_g, new Group({ x, y })] + [this, g, Group.zero, new Group({ x, y })] ); } } From 68a83ba4bfa4f9b9acac9268841c5a7d7e09cbb3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 11:02:55 +0200 Subject: [PATCH 0139/1215] improve greater than (or equal) --- src/lib/field.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 18e7a5efe5..29193d64db 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -748,8 +748,7 @@ class Field { * @return A {@link Bool} representing if this {@link Field} is greater than another "field-like" value. */ greaterThan(y: Field | bigint | number | string) { - // TODO: this is less efficient than possible for equivalence with ml - return this.lessThanOrEqual(y).not(); + return Field.from(y).lessThan(this); } /** @@ -776,8 +775,7 @@ class Field { * @return A {@link Bool} representing if this {@link Field} is greater than or equal another "field-like" value. */ greaterThanOrEqual(y: Field | bigint | number | string) { - // TODO: this is less efficient than possible for equivalence with ml - return this.lessThan(y).not(); + return Field.from(y).lessThanOrEqual(this); } /** From 4c2d65d6cd4b942761c7e2489d9263d61afa7fc2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 Oct 2023 11:05:13 +0200 Subject: [PATCH 0140/1215] changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb07fb4ec9..cf57ec96e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/045faa7...HEAD) +### Breaking changes + +- Constraint optimizations in core Field methods cause breaking changes to most verification keys https://github.com/o1-labs/o1js/pull/1171 + ### Added -- New API available under the `Leghtnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 +- `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) From 7bc025b248e8ccc19f18b23f4d1e647255a6cbe4 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 30 Sep 2023 02:38:21 +0200 Subject: [PATCH 0141/1215] expose range check0 gate --- src/snarky.d.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 1843df88bc..a50caf02ad 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -273,6 +273,25 @@ declare const Snarky: { ]; }; + gates: { + rangeCheck0( + v0: FieldVar, + v0p: [0, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar], + v0c: [ + 0, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar + ], + compact: FieldConst + ): void; + }; + bool: { not(x: BoolVar): BoolVar; From ebc47bc4d194526b5271a1f6736054e804f04d9a Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 30 Sep 2023 06:38:10 +0200 Subject: [PATCH 0142/1215] add wrapper that does 64-bit range check --- src/lib/gates.ts | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/lib/gates.ts diff --git a/src/lib/gates.ts b/src/lib/gates.ts new file mode 100644 index 0000000000..503c4d2c6a --- /dev/null +++ b/src/lib/gates.ts @@ -0,0 +1,49 @@ +import { Snarky } from '../snarky.js'; +import { FieldVar, FieldConst, type Field } from './field.js'; + +export { rangeCheck64 }; + +/** + * Asserts that x is at most 64 bits + */ +function rangeCheck64(x: Field) { + let [, x0, x2, x4, x6, x8, x10, x12, x14] = Snarky.exists(8, () => { + let xx = x.toBigInt(); + // crumbs (2-bit limbs) + return [ + 0, + getBits(xx, 0, 2), + getBits(xx, 2, 2), + getBits(xx, 4, 2), + getBits(xx, 6, 2), + getBits(xx, 8, 2), + getBits(xx, 10, 2), + getBits(xx, 12, 2), + getBits(xx, 14, 2), + ]; + }); + // 12-bit limbs + let [, x16, x28, x40, x52] = Snarky.exists(4, () => { + let xx = x.toBigInt(); + return [ + 0, + getBits(xx, 16, 12), + getBits(xx, 28, 12), + getBits(xx, 40, 12), + getBits(xx, 52, 12), + ]; + }); + Snarky.gates.rangeCheck0( + x.value, + [0, FieldVar[0], FieldVar[0], x52, x40, x28, x16], + [0, x14, x12, x10, x8, x6, x4, x2, x0], + // not using compact mode + FieldConst[0] + ); +} + +function getBits(x: bigint, start: number, length: number) { + return FieldConst.fromBigint( + (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n) + ); +} From b0043eee298582e7df625a7438b05830dfdf6209 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 30 Sep 2023 06:38:34 +0200 Subject: [PATCH 0143/1215] use new range check for uint64 --- src/lib/int.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 39744a84cf..d258ad76f5 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,6 +3,7 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; +import { rangeCheck64 } from './gates.js'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -66,12 +67,13 @@ class UInt64 extends CircuitValue { } static check(x: UInt64) { - let actual = x.value.rangeCheckHelper(64); - actual.assertEquals(x.value); + rangeCheck64(x.value); } + static toInput(x: UInt64): HashInput { return { packed: [[x.value, 64]] }; } + /** * Encodes this structure into a JSON-like object. */ From 454bbd69f0defadd46181bf29291eb213950092e Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 11 Oct 2023 00:34:29 +0200 Subject: [PATCH 0144/1215] simple tests --- src/examples/ex02_root.ts | 14 +++++------ src/examples/ex02_root_program.ts | 42 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/examples/ex02_root_program.ts diff --git a/src/examples/ex02_root.ts b/src/examples/ex02_root.ts index 2bdaaadfd8..31dd48e818 100644 --- a/src/examples/ex02_root.ts +++ b/src/examples/ex02_root.ts @@ -1,6 +1,4 @@ -import { Field, Circuit, circuitMain, public_, isReady } from 'o1js'; - -await isReady; +import { Field, Circuit, circuitMain, public_, UInt64 } from 'o1js'; /* Exercise 2: @@ -11,9 +9,9 @@ Prove: class Main extends Circuit { @circuitMain - static main(y: Field, @public_ x: Field) { + static main(@public_ y: Field, x: UInt64) { let y3 = y.square().mul(y); - y3.assertEquals(x); + y3.assertEquals(x.value); } } @@ -24,15 +22,15 @@ console.timeEnd('generating keypair...'); console.log('prove...'); console.time('prove...'); -const x = new Field(8); +const x = UInt64.from(8); const y = new Field(2); -const proof = await Main.prove([y], [x], kp); +const proof = await Main.prove([x], [y], kp); console.timeEnd('prove...'); console.log('verify...'); console.time('verify...'); let vk = kp.verificationKey(); -let ok = await Main.verify([x], vk, proof); +let ok = await Main.verify([y], vk, proof); console.timeEnd('verify...'); console.log('ok?', ok); diff --git a/src/examples/ex02_root_program.ts b/src/examples/ex02_root_program.ts new file mode 100644 index 0000000000..c4115dbd8d --- /dev/null +++ b/src/examples/ex02_root_program.ts @@ -0,0 +1,42 @@ +import { + Field, + Circuit, + circuitMain, + public_, + UInt64, + Experimental, +} from 'o1js'; + +let { ZkProgram } = Experimental; + +const Main = ZkProgram({ + publicInput: Field, + methods: { + main: { + privateInputs: [UInt64], + method(y: Field, x: UInt64) { + let y3 = y.square().mul(y); + y3.assertEquals(x.value); + }, + }, + }, +}); + +console.log('generating keypair...'); +console.time('generating keypair...'); +const kp = await Main.compile(); +console.timeEnd('generating keypair...'); + +console.log('prove...'); +console.time('prove...'); +const x = UInt64.from(8); +const y = new Field(2); +const proof = await Main.main(y, x); +console.timeEnd('prove...'); + +console.log('verify...'); +console.time('verify...'); +let ok = await Main.verify(proof); +console.timeEnd('verify...'); + +console.log('ok?', ok); From 272b6ed169406b6f834805622c107f14dbb52dc4 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 11 Oct 2023 00:54:16 +0200 Subject: [PATCH 0145/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 014c1f245e..315173431f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 014c1f245ed620c3c1e4a939aa36a728ad148cc3 +Subproject commit 315173431ff77195d8ca1d29b8cde207421f83ff From fa314a08c763aacc6b3120c9e992fdc503f0d6c6 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 11 Oct 2023 00:54:37 +0200 Subject: [PATCH 0146/1215] expose feature flag option --- src/snarky.d.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index a50caf02ad..b86249f24c 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -376,15 +376,31 @@ declare const Snarky: { }; }; +type GateType = + | 'Zero' + | 'Generic' + | 'Poseidon' + | 'CompleteAdd' + | 'VarbaseMul' + | 'EndoMul' + | 'EndoMulScalar' + | 'Lookup' + | 'RangeCheck0' + | 'RangeCheck1' + | 'RoreignFieldAdd' + | 'ForeignFieldMul' + | 'Xor16' + | 'Rot64'; + type JsonGate = { - typ: string; + typ: GateType; wires: { row: number; col: number }[]; coeffs: string[]; }; type JsonConstraintSystem = { gates: JsonGate[]; public_input_size: number }; type Gate = { - type: string; + type: GateType; wires: { row: number; col: number }[]; coeffs: string[]; }; @@ -524,6 +540,17 @@ declare namespace Pickles { previousStatements: MlArray>; shouldVerify: MlArray; }; + featureFlags: [ + _: 0, + rangeCheck0: MlBool, + rangeCheck1: MlBool, + foreignFieldAdd: MlBool, + foreignFieldMul: MlBool, + xor: MlBool, + rot: MlBool, + lookup: MlBool, + runtimeTables: MlBool + ]; proofsToVerify: MlArray<{ isSelf: true } | { isSelf: false; tag: unknown }>; }; type Prover = ( From 01ea77cfb553e0d343f647ddbff6eb9390293d33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 09:35:32 +0200 Subject: [PATCH 0147/1215] adapt proof system interfaces to pass feature flags --- src/lib/proof_system.ts | 88 ++++++++++++++++++++++++++++++----------- src/lib/zkapp.ts | 16 ++++---- src/snarky.d.ts | 33 +++++++++++++++- 3 files changed, 106 insertions(+), 31 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 36cb0e154f..51f3e332cb 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -4,7 +4,13 @@ import { EmptyVoid, } from '../bindings/lib/generic.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; -import { ProvablePure, Pickles } from '../snarky.js'; +import { + ProvablePure, + Pickles, + FeatureFlags, + MlFeatureFlags, + Gate, +} from '../snarky.js'; import { Field, Bool } from './core.js'; import { FlexibleProvable, @@ -18,7 +24,7 @@ import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; -import { MlArray, MlTuple } from './ml/base.js'; +import { MlArray, MlBool, MlTuple } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; @@ -249,6 +255,12 @@ function ZkProgram< let methodFunctions = keys.map((key) => methods[key].method); let maxProofsVerified = getMaxProofsVerified(methodIntfs); + function analyzeMethods() { + return methodIntfs.map((methodEntry, i) => + analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) + ); + } + let compileOutput: | { provers: Pickles.Prover[]; @@ -260,14 +272,17 @@ function ZkProgram< | undefined; async function compile() { - let { provers, verify, verificationKey } = await compileProgram( + let methodsMeta = analyzeMethods(); + let gates = Object.values(methodsMeta).map(({ gates }) => gates); + let { provers, verify, verificationKey } = await compileProgram({ publicInputType, publicOutputType, methodIntfs, - methodFunctions, - selfTag, - config.overrideWrapDomain - ); + methods: methodFunctions, + gates, + proofSystemTag: selfTag, + overrideWrapDomain: config.overrideWrapDomain, + }); compileOutput = { provers, verify }; return { verificationKey: verificationKey.data }; } @@ -352,12 +367,6 @@ function ZkProgram< return hash.toBigInt().toString(16); } - function analyzeMethods() { - return methodIntfs.map((methodEntry, i) => - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) - ); - } - return Object.assign( selfTag, { @@ -500,21 +509,31 @@ type MethodInterface = { // reasonable default choice for `overrideWrapDomain` const maxProofsToWrapDomain = { 0: 0, 1: 1, 2: 1 } as const; -async function compileProgram( - publicInputType: ProvablePure, - publicOutputType: ProvablePure, - methodIntfs: MethodInterface[], - methods: ((...args: any) => void)[], - proofSystemTag: { name: string }, - overrideWrapDomain?: 0 | 1 | 2 -) { +async function compileProgram({ + publicInputType, + publicOutputType, + methodIntfs, + methods, + gates, + proofSystemTag, + overrideWrapDomain, +}: { + publicInputType: ProvablePure; + publicOutputType: ProvablePure; + methodIntfs: MethodInterface[]; + methods: ((...args: any) => void)[]; + gates: Gate[][]; + proofSystemTag: { name: string }; + overrideWrapDomain?: 0 | 1 | 2; +}) { let rules = methodIntfs.map((methodEntry, i) => picklesRuleFromFunction( publicInputType, publicOutputType, methods[i], proofSystemTag, - methodEntry + methodEntry, + gates[i] ) ); let maxProofs = getMaxProofsVerified(methodIntfs); @@ -589,7 +608,8 @@ function picklesRuleFromFunction( publicOutputType: ProvablePure, func: (...args: unknown[]) => any, proofSystemTag: { name: string }, - { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface + { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, + gates: Gate[] ): Pickles.Rule { function main(publicInput: MlFieldArray): ReturnType { let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get(); @@ -664,9 +684,13 @@ function picklesRuleFromFunction( return { isSelf: false, tag: compiledTag }; } }); + + let featureFlags = computeFeatureFlags(gates); + return { identifier: methodName, main, + featureFlags, proofsToVerify: MlArray.to(proofsToVerify), }; } @@ -822,6 +846,24 @@ function dummyBase64Proof() { return withThreadPool(async () => Pickles.dummyBase64Proof()); } +function computeFeatureFlags(gates: Gate[]): MlFeatureFlags { + throw 'todo'; +} + +function featureFlagsToMl(flags: FeatureFlags): MlFeatureFlags { + return [ + 0, + MlBool(flags.rangeCheck0), + MlBool(flags.rangeCheck1), + MlBool(flags.foreignFieldAdd), + MlBool(flags.foreignFieldMul), + MlBool(flags.xor), + MlBool(flags.rot), + MlBool(flags.lookup), + MlBool(flags.runtimeTables), + ]; +} + // helpers for circuit context function Prover() { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 50b1265441..9920031a42 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -674,19 +674,21 @@ class SmartContract { (instance as any)[methodName](publicInput, ...args); }; }); - // run methods once to get information that we need already at compile time - this.analyzeMethods(); + // run methods once to get information tshat we need already at compile time + let methodsMeta = this.analyzeMethods(); + let gates = Object.values(methodsMeta).map(({ gates }) => gates); let { verificationKey: verificationKey_, provers, verify, - } = await compileProgram( - ZkappPublicInput, - Empty, + } = await compileProgram({ + publicInputType: ZkappPublicInput, + publicOutputType: Empty, methodIntfs, methods, - this - ); + gates, + proofSystemTag: this, + }); let verificationKey = { data: verificationKey_.data, hash: Field(verificationKey_.hash), diff --git a/src/snarky.d.ts b/src/snarky.d.ts index b86249f24c..25e4eb6aa8 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -15,7 +15,15 @@ import type { MlHashInput } from './lib/ml/conversion.js'; export { ProvablePure, Provable, Ledger, Pickles, Gate }; // internal -export { Snarky, Test, JsonGate, MlPublicKey, MlPublicKeyVar }; +export { + Snarky, + Test, + JsonGate, + MlPublicKey, + MlPublicKeyVar, + FeatureFlags, + MlFeatureFlags, +}; /** * `Provable` is the general circuit type interface in o1js. `Provable` interface describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. @@ -530,6 +538,29 @@ declare const Test: { }; }; +type FeatureFlags = { + rangeCheck0: boolean; + rangeCheck1: boolean; + foreignFieldAdd: boolean; + foreignFieldMul: boolean; + xor: boolean; + rot: boolean; + lookup: boolean; + runtimeTables: boolean; +}; + +type MlFeatureFlags = [ + _: 0, + rangeCheck0: MlBool, + rangeCheck1: MlBool, + foreignFieldAdd: MlBool, + foreignFieldMul: MlBool, + xor: MlBool, + rot: MlBool, + lookup: MlBool, + runtimeTables: MlBool +]; + declare namespace Pickles { type Proof = unknown; // opaque to js type Statement = [_: 0, publicInput: MlArray, publicOutput: MlArray]; From 09ef5622c26e36445862253dab38d022ebb6ff33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 09:53:30 +0200 Subject: [PATCH 0148/1215] fixup --- src/snarky.d.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 25e4eb6aa8..7a0f16f129 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -395,7 +395,7 @@ type GateType = | 'Lookup' | 'RangeCheck0' | 'RangeCheck1' - | 'RoreignFieldAdd' + | 'ForeignFieldAdd' | 'ForeignFieldMul' | 'Xor16' | 'Rot64'; @@ -571,17 +571,7 @@ declare namespace Pickles { previousStatements: MlArray>; shouldVerify: MlArray; }; - featureFlags: [ - _: 0, - rangeCheck0: MlBool, - rangeCheck1: MlBool, - foreignFieldAdd: MlBool, - foreignFieldMul: MlBool, - xor: MlBool, - rot: MlBool, - lookup: MlBool, - runtimeTables: MlBool - ]; + featureFlags: MlFeatureFlags; proofsToVerify: MlArray<{ isSelf: true } | { isSelf: false; tag: unknown }>; }; type Prover = ( From 173e54eabd330d0638d6894dd8f07b566e767bee Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 09:56:54 +0200 Subject: [PATCH 0149/1215] compute feature flags from circuit --- src/lib/proof_system.ts | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 51f3e332cb..d7f1275158 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -847,10 +847,43 @@ function dummyBase64Proof() { } function computeFeatureFlags(gates: Gate[]): MlFeatureFlags { - throw 'todo'; -} - -function featureFlagsToMl(flags: FeatureFlags): MlFeatureFlags { + let flags: FeatureFlags = { + rangeCheck0: false, + rangeCheck1: false, + foreignFieldAdd: false, + foreignFieldMul: false, + xor: false, + rot: false, + lookup: false, + runtimeTables: false, + }; + for (let gate of gates) { + switch (gate.type) { + case 'RangeCheck0': + flags.rangeCheck0 = true; + break; + case 'RangeCheck1': + flags.rangeCheck1 = true; + break; + case 'ForeignFieldAdd': + flags.foreignFieldAdd = true; + break; + case 'ForeignFieldMul': + flags.foreignFieldMul = true; + break; + case 'Xor16': + flags.xor = true; + break; + case 'Rot64': + flags.rot = true; + break; + case 'Lookup': + flags.lookup = true; + break; + default: + break; + } + } return [ 0, MlBool(flags.rangeCheck0), From 0d777d528864fb1856f301e1d9879f6f37647cdf Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 10:31:15 +0200 Subject: [PATCH 0150/1215] ml option helpers --- src/lib/ml/base.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index cf68579ce0..1b6b7fd02a 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -77,5 +77,11 @@ const MlOption = Object.assign( if (option === undefined) return 0; return [0, map(option)]; }, + isNone(option: MlOption): option is 0 { + return option === 0; + }, + isSome(option: MlOption): option is [0, T] { + return option !== 0; + }, } ); From b0277398f745d89c4c7fa890e6361e75e6b123c2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 11:29:58 +0200 Subject: [PATCH 0151/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 315173431f..91e701cd02 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 315173431ff77195d8ca1d29b8cde207421f83ff +Subproject commit 91e701cd027d5a1213e750e5e9b80da80c9d211e From c7ca867d8138bd8a3357eeaaba80a7add7b06ebf Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 11:46:23 +0200 Subject: [PATCH 0152/1215] change hard-code constraint count in unit test --- src/lib/proof_system.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index e7ec592915..7281070229 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -46,4 +46,4 @@ const CounterProgram = ZkProgram({ }); const incrementMethodMetadata = CounterProgram.analyzeMethods()[0]; -expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 18 })); +expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 9 })); From 0630d5f47772f0b91ad705e93f16be748636a935 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 12:02:46 +0200 Subject: [PATCH 0153/1215] fix gates computation for contracts that use extends and have method names duplicated :/ --- src/lib/proof_system.ts | 2 +- src/lib/zkapp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index d7f1275158..2313f2623f 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -273,7 +273,7 @@ function ZkProgram< async function compile() { let methodsMeta = analyzeMethods(); - let gates = Object.values(methodsMeta).map(({ gates }) => gates); + let gates = methodsMeta.map((m) => m.gates); let { provers, verify, verificationKey } = await compileProgram({ publicInputType, publicOutputType, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 9920031a42..fb03bcbd92 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -676,7 +676,7 @@ class SmartContract { }); // run methods once to get information tshat we need already at compile time let methodsMeta = this.analyzeMethods(); - let gates = Object.values(methodsMeta).map(({ gates }) => gates); + let gates = methodIntfs.map((intf) => methodsMeta[intf.methodName].gates); let { verificationKey: verificationKey_, provers, From 1bfedba0af6e743e18f483d8cd252aedf6f7334a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 12:29:01 +0200 Subject: [PATCH 0154/1215] wrap range check 64 in a gadget that handles the constant case --- src/lib/gadgets/range-check.ts | 14 ++++++++++++++ src/lib/int.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/lib/gadgets/range-check.ts diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts new file mode 100644 index 0000000000..12a914ccd8 --- /dev/null +++ b/src/lib/gadgets/range-check.ts @@ -0,0 +1,14 @@ +import { type Field } from '../field.js'; +import * as Gates from '../gates.js'; + +export { rangeCheck64 }; + +function rangeCheck64(x: Field) { + if (x.isConstant()) { + if (x.toBigInt() >= 1n << 64n) { + throw Error(`rangeCheck64: expected field to fit in 64 bits, got ${x}`); + } + } else { + Gates.rangeCheck64(x); + } +} diff --git a/src/lib/int.ts b/src/lib/int.ts index d258ad76f5..451ceb8610 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,7 +3,7 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { rangeCheck64 } from './gates.js'; +import { rangeCheck64 } from './gadgets/range-check.js'; // external API export { UInt32, UInt64, Int64, Sign }; From a8f06d1b9b5bcd7925052bc320cbbad275dffac8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 12:30:44 +0200 Subject: [PATCH 0155/1215] dump vks --- src/examples/regression_test.json | 96 +++++++++++++++---------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 4b8abc7521..5e2ac98d4b 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -1,52 +1,52 @@ { "Voting_": { - "digest": "2f0ca1dca22d7af925d31d23f3d4e37686ec1755a9f069063ae3b6300f6b3920", + "digest": "228252bac5c9d0b4e04299bd2d314cf0787a2dd4448d103e4aa4c1f5d6d6a338", "methods": { "voterRegistration": { - "rows": 1260, - "digest": "5d6fa3b1f0f781fba8894da098827088" + "rows": 1254, + "digest": "239c744df7c27860cf346dd79f316431" }, "candidateRegistration": { - "rows": 1260, - "digest": "abf93b4d926a8743bf622da4ee0f88b2" + "rows": 1254, + "digest": "0d63868117a2ce0e774bb1312869c43d" }, "approveRegistrations": { "rows": 1146, "digest": "197ce95e1895f940278034d20ab24274" }, "vote": { - "rows": 1674, - "digest": "a0dd0be93734dc39d52fc7d60d0f0bab" + "rows": 1668, + "digest": "f73f9bb98275f369b06a2273505f4665" }, "countVotes": { - "rows": 5797, - "digest": "9c5503e03dcbb6b04c907eb1da43e26e" + "rows": 5700, + "digest": "b42a59c5ce11a0ece0d0878d0a9b21f5" } }, "verificationKey": { - "data": "AAAmF+ylfZXG6glBTCjrbm2BTEp6JMdPJQu1cfN7LMLMG9gk6jRP1pnY7DE9mENmLsuIDJGf/d67ouH6IaWle40ErmhSboXVyvw0w875YFdkyY4/ZiZNJatYwjozv5zroxM/b3ZVe/SIImxhtJnw74M3gcVufpHb7CFWx4KTBUxmF6PBw90hLKLjZ6EcSWJTwyBp9eQfr0FR4R3JY0BOd7w3NawTRi679tCO4DqW3ruFEWdhNB6q6yp61vWoByfeJSck8OFDjtOio45p8mab1wfBeFLe0vL6/xGfJ57cN5JvINvbMju8/+7a8Z7cAA1A3s+B3h0E2vdNMDHQknd8SOUzoMa4wmOjSanjlVVB5BjmP/eAKtTq5wnNBAF+vRLdrQyyIBNRGwm9YgntSZaV0lz2eGtrlMyYsBr7zQDo2VAiHNadlEKh+9thtnp8BkjqQF3vY5UDZ5KH8+mKB00hyj0ezr9Zsky7PZv9rE5YiLQI68ZgUYZQVq75QdjjQ3UsrDaB8NcjuobySjvCruRBENKU2bPDz5UdYIoVTifTqBMYLTx1LnaMFMV8ygOOL4cwxbRPJVZAkxF+u5GCarccbrweAB/glU6yYza/pWpxm1zI9gKuN1BZO/pRrbq/DTecFSk8mI31Jv069cMu/x+0ZXlc541p2tBf7gpfUtcwJJ2Z5grHdL/8HsevJYcZRD6Jht/GTEUK8pc9TMIqxZJpdKc5MYB8+UQv14YnVyf7waWO3d0p0l3orHk71cJ/iuTA/3cNGfzWClrrpoGkN8DM2YA9CFxh7fePCuAvkw+eqqqrohGiaZv8ImYGkl6+GAk6GzUfAWBWWZdxLB5lQdeUJwfXOTPbGc5fH+8UK8eRCjrcHVhQFvDWcqagh3k4rPVZH4YzlEOIbMacKIbtLLxMX9NzMbHamvCZVI5NVluJ1W4Fxy+oPjZSRBAIuvhVXtIpiACJ2L/Ca9ALZsOxQ4FoINWkBtjMqaY9vcqBvY82KOzUncdZlpIVTYkqmlJKiePsEgMu1i4dtIUKsH1p7QJ7dztEgwrcGokakQGb34J98u/rojWME5HAprMIOeu3mQqxOUB88t2LhOwGuvOK4AemsmFvLx7zIOVowmK2nYcL6W1iiQ7ZC+mqWluJdyPMCkHDIAwN41VfnE25bqks91vPVpREQM66IMnCjlxKP6oiJU/AKyghnNsVLHOnVLHKBREpSdssFcDj24k5R7Cn9/8oQA0nEIrml++bB357GnzIZMkXy5RHUAg9EeKH6GwkX30EftI/HxA3eT5/G+Ly+W5tIh0G8YuZjEdsY0MbaD44/P5iajfftJelHSXI6RJ8+C3UXNkjDfNE1ayOoGqE8Gbnw242I9Uc1xdapNmvjxjnWRIHTD58VP1CpbQiaBw3cJBi0GwFDtggo2s502bdfQwsK3RK+tstKlDGDrPM9hYGDeRNPSmcNbMM2qdIyPTUkMqTwZr09JnLyOR/Yls4LZjQ1afrG13WAZVrp9o1GgtE4SDZ+DVZ+2xRvrXO1p2C7Tp0/qcJmqcImhVyBo8PwdODdvaP47s1YbegUvDrwU/BuO06ZBxRgHZU+anO6pzZiVeHsOlpulOXXAnvPWlJbMyF2cAOCWeNCzkles550yp5Alw7Fjd/kRiwefjqTDrOaAbUOYQjyAB7isaEm4nBtwNWeHIChZjI3ezgZmHYuvC6OT6g6AQWZGXjMNlPR5xQMN3e9hg4mgE4CLUDgzSJCkGIhc1aBdd+IlLqwG3e5mT5SDtqS2vyL10v/2Bq6KRIU53ViRwN8olYKsgSQaeY3xdoTS7LVoRX40N/9T2N6yjIFwuqyRq4Kvm/TY9+AfVy2TFg8Y/ZmXBdWLAwN2wO8mRTkZS1LwAuIpPCOIlRcvelI75+5bNexLtzflOUjDQ+Zlzeg/61IagGdRIIdmBol1qVQUZIXeLmFEcztSOaqjRPTuhYOQYdPovz3ORH1b7VZo/uPPmm1nAl0Hsy7jRwXuCZG8gOJzRuZsKGc1ExdYqz6f9VckVly8Zg4iDWTr4FrIyi86YoAwh5slrh45xpLnHGq19qiU1PxMt6Ekv9TP3qdcXJxJ0xQHkENLvQMQEY4Pwf/+iJJbv2IGYDqA2osSJDMA+jmCw8ivkJKuivI7j93K0tB8cXUDK8Zc+A7L7pZ0YEr3nwIhscSE13iaauKRnKunVrYU4kYoXTNSeKUOBLjlyFKmoH2/RMqhXuzbbC7D9gYaGx5pNip0mFIy8Cvegk1048IS1f6ow+xlQLkUZKCwQWkATl4GltQtPE8PsmY9PrPnG+KKDS+GMJIXXL9+2Jgj90hqwZsF7Axp6gjpM+Y4FkFqksGdZ2JsLrcFhbeSE1/vMn7hCznbthYwOZ3Tjalve52Tk=", - "hash": "21462580856298000408039819846448252784015090129029961950407537341141126974675" + "data": "AABH39X4mhCRxxmQaOWL5gTcmOQHph2XtIMq/1p2RDq7GC9wuwD5dp2R5G0ypoZP9C/li5DHsfQVMXM4QL2aos0S5Id+Enjp29VOy6ZuJHLFt/B8V/fiPv5i9QckNaHmNBhboHqEmGwv8ezQrhF/VDCUhv9GPB9KBlmsdQKs2ETuHY5KFvQSLo2/fSAkE/yevXKWeUbZS87ypZr+8vkHlb025PlbjwN9lbcdd63U3G9eyzS2PE7jYZeKHvYcN9WTpTT2cQNHy6ZC5m9QQlDKyh0E3LCYij+HgEPEWAbxeddJKwAo8ygBK11uvHNMW4C6uSuwMjFqpDTErCeykTQUkWI//Mg7RcWy5BPGhavH4zUVenQSuLGjMqIIzGQTUu9BVS5u/rwBHDcI6elpafrhkTPrLad85ZNAARxoue+9nip5N5fbRo0k6L2FnWKTNaycAvH8SwvE+w9N6qubI5KcWQYfKyU1/R7i4DaufIygodZEmYpxhvvPn6VI6SHbpEmuIBv04/rFP2Yz0U5NPA+U4Ix8FDRy0qKd0dpFB9WTAkpnFVvdvnOcD65vltBLMRAvx6qqP+hOtqGY4pYy00fnWYwBAESmpTBOp3uBDsz8xW4e58CZITNyAN+mvzKRXW/6pDMXzJp1Pb5ABEw7a53dzLpA8S18OJJwKNMIks8cqrZE3BhterSFJUaM+3Qy3DNWqJks4BPVmjCvEGBXvJmcpku+IWT0XgNCWhSHnU/Exol8pmOOLYcMko5ggKAcYuIv7+osEUd3W9cTznrnpVIvFTSY7pH0f9jg0ENfw0/Arbi0szSl8iunctrnZeo0aGKDvqLWKphUvGor0s7U5YZRoPU3BINnMeTEowWwziTCOPZJOfl0F9/ACOmLyXkhdNWB4D4U8ALlXVpit6aTCzsqBV26ZUHPeD4W/IRbLfWIzM8irz548EQQLENpsZhqTa/q09B+wdEL0hb1AUrmYuH3XpB6D3Akgh0GlMseCEMwyq0bzEWBZuxwt7zASPIdfs20bvINV+DSM6smgdPjj+66UuYGUDyh6y8ZB6Won+t99DvaDi2i2hE9/Qwdr/dxu7GCfjKPqDyDK8RrRosLAxNliQ39OhPFGVUoTl3smqwyhIngN02u64E7sl5rcNZ9E3zETB8AvB34WBX9JoIiBik038JhFIvDG2R9ovtHSQuxIwyvADNN/c4JzoqqrM3iAb3vxmEB1mMobDr+FWB1FsBxzR+6MUpOM8qHO1kR8JPwxOisHph6POrWPieOUQLy07Rx+9wH78gxEIBduNUsx7+NYjDTC7LoOsoCKCDFGByKMEU2WSjL6FhLdUUbUm6cAZZSEgKu4ef+Lckbq14OV/UDPU1uPtDi7qDx6HdQF/q7ftVY8TLjCkaM1908/s+qe02slL0f8FhcpTBBuLmeeQfNqh1b/B8Ff6q52wBBWFg6g2zO2g0WtgMfYu/OJlqfD2VzoCsnyW+hZqS3d6vowyggQvskL5XGYQ/4kZLfyZ0MnJN01eWOTIxFfvFZOuPLN/VnJNMBlKqoTkZHoCO2/NyOpS7kPUVxDTkyGjO+deqTR3MJPx8EdSJn+PNVs7XjNPUsiZxVBtQWhcDxjdNI7PguplDdE6iiHobnobibx8h6CAJjuiPrFWERW4Lv1n8ELXPzG4IihO4ifNiImqHw2sQzSezCFWLvy1EaCLcgY9Co6Wr77xRf8nMm3wGUYcKAu/K2aEcRoDBV82VPCRpxtD/b68fKPNWW9tRxcjxP9iE6Ngg9DEoc18wJv+F0urmIJBuzntgYK9vFZ26XaUxKOQzLKoFoc8yzbPMkgyw66VCg1M7A6j1PHpWehw1n7y0edKVSDV9JcAI9Be9536Yl6YmJqNGdHgAKmqC/AbsTdgKgxqgOjYtPVWqg6cNWTLQ9uQ+Uwkp/LYYqgC1F0bQjXkSr4GnUadIpzvkFZsiJOGwL8O//on4cqImuN13eLTf8aYlrOlgyAfHVhc5sp9KVheU2E65S2iE4DGvC0j0tvTduK4hPbmje2KKEM6qGYvv8lcke4lCqKsTffTebnbolEYhpyoY7O6VD2B0iIgB8px2w9qmsIXg8vr3Q+iKW4UUye53/qGu73a7ArwWaOlE1AtR5NAl9awcf5nTebx1itnFw+vOMKWO24seAziGk9Tm41B7Wn9ItIXZoawFbf7Olgae2XVgKdZreOOCXryQStqMSrQwuf6E3q+safJ4Vj1lrGMo7h+KEMeyHpH/gqJZCGWSJkXKYqx9RFYhic/x8GkbvwhjvUhgoMiTj88UNHVCjRY3PGTA7GBtA5RX4VqmMT+6cJLUujm4/Vg50WxyMKFp1+FhvHFkpdjAAGIVYuNgUwSHGeYPBBTyftAyE+YyIEsfAQDU94Tc=", + "hash": "7666922731910177343774583616275385104993750771441539919862036448025996847992" } }, "Membership_": { - "digest": "fb87402442d6984de28d7bab62deb87ab8a7f46d5c2d59da4980ae6927dd1ec", + "digest": "70ee62d196708a16cfe8fd7147fa6880c38bc2fe0b2f9948d3c56ebadcded6b", "methods": { "addEntry": { - "rows": 1354, - "digest": "bdb5dd653383ec699e25e418135aeec0" + "rows": 1348, + "digest": "9a7080f673496425c2ff9ea344b3f133" }, "isMember": { - "rows": 470, - "digest": "f1b63907b48c40cd01b1d34166fa86eb" + "rows": 467, + "digest": "15db80b7de333bf1da8df93df005b5d6" }, "publish": { - "rows": 695, - "digest": "c6be510154c0e624a41756ad349df9de" + "rows": 688, + "digest": "7a8bddfecbf7c88fc697eac5e9d7c5dd" } }, "verificationKey": { - "data": "AABoEASTjb57lVvHHkX/S1bbJFKReCgeS+fs0hEX41xWFGi+iz2GxWLP9qoCdBFbkaueRy33tn6JgWe/uOx0GlYX99RUUltDLh2wnqOckf19P3Yu4My0BPcyY6lArTlZmxYgN+p4jAg1RqeKBKB6vwjA5yG0qxMA32a9iwk1t8BeBuR4E/oS3QgckSt7mmNgqvSMa9IrlMe4eXxw/+blx6sH2gxtSAbXp2gevWZq8p5V5kSvGWI3Nlj+mhKGEexdcQc3VDHQG09gPMYfVuOZmqjlKla9A8k14U1pxT2wad9pJtbTmsY1O5NrfLeYY6oQsd6QMM1bmg7DWdOCFmcsyqUqL/0rV8Elo449eZoXfAHKMX/0tJN41vEsVQKrUJnzGDiv9QhkzHRPCano4ntvPPhT97FQIJ4Ug1w0GQ3AGssYIC0URVUhydPc5zU2+DkIhT2ROrnFzKFrKeNvkJmXjEYhukdWjKPx0fDjk1YqVHlEBv5RkXNo+8TY4p4XnwrYjALifnQ527mBj4rW+LUTPPZqdj+rCLYNORVFCv9ca5YMBD8ZwLE5XxfrDhXBBH1wFJwqkhYWNiHRAiCiYPa4Bv0LAL31fOJWzipW4zfeqdQXKBnbOBcRJWSsFDEiNst5TD0IPn84DZ13H5lXeKIF0NyhQKHsWAefDS+fwAUorKIJZCNyUuKqzfAGCUt1HOa1dGfSTxCrFZphJimZE3q9uen/OSjdNmLbp7UpVYXgPCmAk+3qreP3rMLYoxEFdQGiAtInWLmZ2H6ZhP14MLBPUDnW4FnvR3SCxmg9VW/WWLK+NTxr07zq4HHlFs1MG4/2G4VNwJN/t98eLL6eqxGye2I/OshpizJhaxOhnLJpFwWcjf3I32fIdurv1htpWhXGVcAmbpzwXh/s7wvuCEvdpabhEFOgQyMfhGe+kCy57Cak9hSSYVoEDMBd5hjkl7kZvyajLzc7Tms0lpHok7XpEZEZMg+MZtbv4mGkCtkkyFp2EZh70tUvMG5OCFcqzyUGvCoWEBLyna/qORsPWyFUZo5Ma9HZA9tlkl77GUid1YNLUAyQ98l3cdY7htqHpi3sO1g3cK9T5JCB0ZgwFTSTa2rqG7hHiPNuf8S+MNyc8YVEgGE4Xhxv9ZJo9yug9TGaYkoLsTT8tr0DuJpIzPp5qaeGgAu3U6bvDrSJiIgAYW+oCBsxjoqv4df4DHJImLcumThOGx/6u+yn4E+gV/F9ydqWGy2qztI0icDm+pRUxADA1Hrxhl8pA1hMEJzxTbi3eIEt/tlnsN8WNeXwiuCv1Ey3+QjAEIZwRYOAfvMe3k4KMSCSD3SDqewZwq9D8RUCOSdNsDLOKvBZmdKdjE+TTQUuHBcc+fRpDocNmgROwc8Yp0N1tTQLUvFvZoLYwdlBkM0gqZZafKAexrc9LKIYifw6z8WlD0b7dSBLH1g2151SNAKYpXZcsYLuZAyRH2tNpncaF69wGqmUjoMV0MYztDXGFoVk01oamPCiUtBbFk5yqTHsYY5K7shfvW1X50SL3po4CuJh7LRbhfz52ukdfvI0NSC0QWlztJDaMVPPi+biXTwwiHr9bLB5YTZe2SD3cMXKAdq1LY1A4OY3DH93VbcvId4pRb0IefVhmQsihMqW3BrkvU8IVBQGkt8I/wCWCdsSSnq8vOjfJto89AL6dK+Q2eE/OMF2r3mcI53ErA2RLh6ECLQvFI8w6rBBuXd8d9acMfaRdD1ce2fy59/rDtNjBLQkZ97QQbAQv8WZs7oraJ5ap8cYRsh39UwwU60KMDk4FFxOBwPgk4XYm1Hvk+LWDm6M5drSe8wHfa9XePoxvC+5xX8UynCMvCXDGgadVpUSgBfIY4/HW2lwgtWm0YbNJACmcfzB5CV6aGEWaHe6+JDAV4HHDGjdRXAvoX4anzfHOgwNPt9zVCbDpJ+Tn0ukgwQlVbLi3i8ZY1WomNOghT8OLobJDArhQYN750/y/oDl4mdYtM1waD1RKLU+Rg6ZtxIFDbhNVeWrffiixWwr+vCai/3NzNuNYaJ8F+ljeu4cBM45PHDoHk+GYmNiMcyiWBqtoSh+Z6TJLAqKdbJM1/MyxT4iyXVMLilrgYwzkO+iEKlHlxyRENTcN8Fh4y7t4CevdShnlxZzMe8oLezOHqaTS5chpQcil7FUuoO+DuvLF3JJoFG4Uz4dGv3HbiZfyp2KDw8Wrxt8xXs0VKmkJK0bWAIOXuCbprcwBTc5UcXRso/7qMATamgk1qdmzeZE0ihQhjhjSNQUQTdVTQU2ZQyPxBY/Bd3YZgRr0aXFJcJkNp92anL09yaNAxwWAwx9aARstBYydWv4/XMf8LJ9rgg3DrsF5tylpWMKKt/15onN4HqcjsKHuCnbvBFhVJXpSx4=", - "hash": "10521275425053135358416581035189809085015014181948017829202402642785509990588" + "data": "AAAkk4OmiOCy3uol0f1+0ubY5GZ3UPRk5/2NjGxlBHE9MdpXE8G8Z1OmP6pzbgVqkVXK52TJW3W94Ggs3CeXc7YrYdaue/7xkh66rtkFgNf9f+EBJRlqQCC3Jstvdv/eDxUqV8JdjWz1djX+qp2kPZ5e8kOEgMNkKu225wGfg7JxJS/6yRHD6AeIGtzD9brwg9Gi2i77tZajJGuXOFNCtzcVtdwZEXVvMBQKJ/afh5H7FK4PCFwo267EWLKzo2kQbzmRG/tmqXZRF/+CyTrBz1KKADcaaun0M3IBCICw9R7CIo5041GxiPeV+8jHNouXNI3xo3mHA0EoAhBXTpEevKoh4JBXdbow7E5sUnwpEjToHLZI+Wgx4lC/rMbPA0TyphqJZoc/hhr41sDEg8qsOgytgFM+O9ev75o55by0tpmoKjJOVqAt6ocw7fLuL8O4nrw31GIwGNxapTkgSDJXeFkzVDWQx79xBIXKrRxQgmGU2Y7k0llESljnkpHVR1bG7Alno8blc20jowkgwqdLEv6f82X/YwVvpabgPxX3IXBAFm+ajYIpnUuaoG64pnuuixsHu7Ggz4MOulqF3T2c+IoBAD48fPSwgWOTlB6C6PZqifzszip+8PEUPh2aWWvJN0IiOh5uW+qhGuuDuKPLDJ/hzbWOe2Vr7xTOz4i77Z45ZjSoTDs+mCx0Tc1ZItfFkQCLQBGdzkB3t7bcEZ+7LtvtCamEg0lU7YHRAjmnS1LSdMShUU5YH8f4RvnNwTK+9ngqKnvvVX2l5U6gd3ip4WVQUJkuNGZLKThk+J8YCPTI/z/osi/GBICG2hIXoOKk1R1I7DCwxEEEPfciz4s53Ah6ADaFqvNb22zdnOoQAHZYAb/DAnzmj3aprN282rVG/nE779AU6D6w2PKSM3qT7jNj6gglURAAoxjv+TM47nioByr2x1zbNUHTf+p+u7p0E0BHh9BNnGUHB7betWzNWtTXC07GXahUFv9IIy/KudEbFkp+f2MGnPyO15oHubah1o4lg0ns8o7aRbv7KZFiQtp9HyFgVitXn5MfO4z1Jgl7czq1/v8Cvb+YvEpecCPqZ6paCBEjXl7Clm9O0riuP7WrMK8x4VfDR3RQ5HL+TVjG+zq3pjGvZDNbKSMI+6GoyjYUi6GW23J2DkFhgZf/1SGdOJKyXQBV3ZIg8u/+q0gFHgFcM9ZOz/JyuOTNETX/v79M5ai3pagleI2qirYT3LblA8kMKe1fApKlOLPxHPQR8CSmp286s4xU3BcMrOtKMPwbO4ozshs69hYCfYTCRUCbzli7ombGnBqK2aicVoC8xy2hX31L+VAAAJUOu3GLaiwHvNk/T5YG/2OWYdVddFDWJzek9xED0K6uvPXYtqFF+nAKhIdbuf7tmdzNrilcW3Ugiz+Q0w15DWxkYQuniMOpWfcqrpgcqim/86MQQ3AVGArE2LFvMvVQYisFws7AAj4id/eip0FxoC5SnbImuB/8AA0dg/IGqDsm0nu8c9R0Nj0Jk6IwhsVtm+S3LjdvwpYFhcWuh25pPGY7jcjH5FC6uPakWwk1OUz3pE6qVsTZAS8peXORu/ULdBsf7OhFl4LVFh2PD6wVOb3sG9NKzEsRLtG8QNncUVFKDl6BoSU6eR3bJrkhuW9gRdrkNXLMJX81vT6w26eina1oJwIkk0PeycEK299CM1eSHWYmFtR7/Ax7PIpohrSKja3OFAo2ROX+criOXzpEejUbmh08liuRDq5FIBXvEb430pNG3fynUj4zHu/YPXeY9Zb74Rng8wAL+T+z4PYAsOiu7lgr6+LzSJRqHqpQUpty05y9oG0tICd5VQXJTu8ISAT87yuX59nqr/NsAZEWz73M7Cyb+TbNCgAjNiwk1eGCFqcXRr4XfryyLzhOuzieM6Y9w3jCaaemEROfOAhkWxomEpryxFc759TQ9pXwqNbof3ywRhNcR1Umiv95PzGv5XAVI4v12tIH62wqr8/1v3hw1OUYRI0gAj5m9mibMJ0UODJS3AgA461emH7w1GRpkPAUGtWvYb6bNhSI7d2tsckofWbl9oZcANyhcKTD6KJU8AICw03IkxwXpzZtoAzcFrg2fK5ar4RkIyiZ2dgFu8UhqhVWwGU9dyLdXA0TAYNWQKFf3cObcjj+OASHXYnHuB9QpaROXUCfJifLsV0MCmEFMhc2j2UJI/PogKFjRsUMHbK2vqo0AUo0i8U1rrVpaBHDhFxpdtr1fFISg9puXoT+0xZqZdA4yxkce/DSpBkSMSzf2KfavkeMrkaFFbdeC5TpY0UBpohPMB4G8kUtT29e8cZWpbBCUNdyTf3vZT2u8SUR8X6FQpA/BxIEwrFKQIuoTJMx+Jbpn9ShFkDBIPi3kdQlQ6tZ3zc=", + "hash": "10639931931786655061770043518608311102827340335227174964879667105855422186408" } }, "HelloWorld": { @@ -63,7 +63,7 @@ } }, "TokenContract": { - "digest": "10ccc8e9c4a5788084a73993036c15ea25a5b2643ad06b76614de7e2910b2cc", + "digest": "30fd633e4127848441cbc31dd252156c965030dff0ce304c029bbbb55d7c6a7d", "methods": { "init": { "rows": 656, @@ -78,62 +78,62 @@ "digest": "9b45cc038648bab83ea14ed64b08aa79" }, "approveUpdate": { - "rows": 1929, - "digest": "5ededd9323d9347dc5c91250c08b5206" + "rows": 1893, + "digest": "01e5d6a2782a1ad03a5a60646edc0fb4" }, "approveAny": { - "rows": 1928, - "digest": "b34bc95ca48bfc7fc09f8d1b84215389" + "rows": 1892, + "digest": "b01837f7ecab9e142e0ad49529d1d680" }, "approveUpdateAndSend": { - "rows": 2322, - "digest": "7359c73a15841a7c958b97cc86da58e6" + "rows": 2280, + "digest": "c6b77e4100ff83308d0e6bd4c95b7e85" }, "transferToAddress": { - "rows": 1045, - "digest": "79bdb6579a9f42dc4ec2d3e9ceedbe1c" + "rows": 1036, + "digest": "6e46a11c369acb33b9b7b52aab2b1925" }, "transferToUpdate": { - "rows": 2327, - "digest": "f7297e43a8082e4677855bca9a206a88" + "rows": 2282, + "digest": "88d7889ee7aaacc2a1c70973b7f09de1" }, "getBalance": { - "rows": 687, - "digest": "738dbad00d454b34d06dd87a346aff11" + "rows": 684, + "digest": "d7ef3bd3809380118fc6237f6390e622" } }, "verificationKey": { - "data": "AAAZwDF1RKCTYA0THXubEEIz2rPDLjxr0qyHA0s1ycjxCNxH03V1odt+L9ccVHLhKFOaGjmlWy2jNTaPu1i8QvEwhvSEdcwDuQvSBXVIwIsFt2KfhAWMak2rupPL+Yx+oApXzgI859KipYtB9cYyKbtTwA/gndNvHRUKiKhsSpe5OxAk0iZRToVR5bGj/oVdJwasvCkd3bE3ef9/LPnF6lIWg8Usxdh4+2Stra2FU6GAY9onRBTTipcK9TxmTiZC3Al6irgSu3HS2k7dNpoJP46I7nadZtBucFC64oUvneu+M8QOqzhJgYv0NYfjdR7WN6owFJBICExQI7I8ig8eY4guYs1OQoQr6677/iwM31wxkl4yJz52FJUzvk6Q19PEHx93lhbGUeParFpdaIqvajTcD47gKzN2DitOGebD+7N6FYjD4byClyJcjrbtpgu/GycYjuN7lL5/3LOkEanA+BsRHD9JH2wY229iHbPaFht6x22QgRR/W8lU94awgYb3hzwwxaZ1vvc9/2WDRQwZmuCnylwUNKbZaNqFQlPLBveqAxYR4bU+ybxoDKBKjOJPxDZvCpBcPonD9KSw8wVppNkRAEvpaVT+PMZ5qL4mgwlH0DUobD3ZoCQ9Q7tQGi3WRR8+uheaFRFV6cdZ8fs1z3DOCn5DOdx1+h/UgqopWJXiXgvxVJCK+34c/BVbOgJD1/EVZ1U71Hr9iE4wiGsQf8lOJQMPic0VzeJOSAe5F1JQdqXV2ffuSJQHgIyxrdF12FEEwGyP0gQmiXK0u6Vk99ucyNtK3zFT2lExl7fqZ1TbhiWSrXCjq3UXD1MO8R08TNPldRdzo1FOLk4qRC74MJs0G41u3CG6O2aS6OSy+OQNvFo3bNPQNadNXiL8MK5bWeUhaifJvWJ1Gu/YDyhv/aiomqnJd9OM0c5U2PyqILTlDxEfLyrNbZ7k0/Md1NtQNOLdk63ClVuJOhF6PPxH3NRbMQU4/nnGIkH72Opr8hvuUq5GzQ4SPr0slgQ2HJtP6BI1+ig9HRyl1BwDERSMFtRhEl4nNciw1hMP9QQUsDhFER0TkjAMH5NQCKr93rwBgR/LjUGe2r8X6fIOyOWvIo+aLUzDOfDTCYc7C/+RWPUf+UPZYCKXUMZ5+JC+eK5/tJoi20czcQTCmyE4UPBvA/1THFv4bnoxWKiZ/tpLVZyVTjpn5RhhokXst2DgpVG3BvfWTQn7Hsfbt77wTRcdoYPJB967UH48rCqxH+Fcr8HK65t+BiuJwJE0H2FKlVVdypAlNhB01pTR9dPxtePA54ip/mcOuqNPq4FVMLrgmMkcYBZ+WzlsGtnFdtfvGlYsKo4vbqGBv5x6LYEYsxz2i6nUDJQq0a496IeSIrXMFZfp/FTlOtrel3QUH7Cwu/CMQrokVFjoZGXQB5Dqs+0dciFhamuFKNHUUHtnRkHo/cXrBwSFaJCDTFPaPxNDb5amQVP3JW3AI3WlZTTI93siA01iDNdUvWFuYrJOGJ3A+W0Tzu7/qzoKCkAMqApbh97tH3wprl9x1AUrRZJjiOTa1Oxi9xPNs72p3jHUef2BSxVr3g20rGQxbH8zxRvuvsqA0HGuMozQvVXfsde1HU1IoEqKJSmx1ay85BzKLlYxj6HYQ8MKA+VUDUudhxIJXy4beS4Xb0wKuu8NqbNQosHiDkV1ApW5FeF5phxkh75RIj1K9CC/X+gekcbwhomWbMVr2Kd4vKzsXW2Ve6f8k/0hnucvFVMItIVmLR4sTFzu6twAbtMrHgYBfQBe3djzcNVJjpgN67K1zp6VLDq62nqnbMNj+O6R4q5AAbfLNqEK6VNPridK2J5h6ox5MPdqPEIaHDGMWkk/RlxCMvUdbENp5ZxTMwAXFyKWhExaMiAC8vdoYwPbwCE/mGTLbwwHGR6EZti+HNe/dGVHZQrxROubh01pe0pSCPgthD/t4RkJYqNztI0vBHyHuzFfpOJx2I+Pz0XSOV7EhjeOXkgEUT3TsxYZNRpCPIoNs7IS8IqggIRjXErnWtX+onpt37YQQjeKbuVgFKqzCygaSAdAI8Xly3rYg0irkJ5cdItcppJ3IKrA5fIMkbsN9KbDtXG8y+zL41NShxDoGTsFsuJj8HZ004zcvBfl9h+RlJCtKAS+vqkg3gDxcupRDQFOIm3TxenSwUDwO7rDh/2PRsAfCbSTFJYZI/dCOan1ad6DTR6sGMVL++wY6QlVaZJ7M8H2msMWsH5XnGBofluUiHkvEzl3lfHmbClf4ML6MmwHvHt9rJusq44pWyFNOMkZVXwFlhmzamb/P2jZcddoD6wT6s2hLRXC1bcv3kLNJzxgm1eOVAK1qewa5bClvBspu1FbqWDN/LJnDOdtF9ipcUiRjtUCU4BQ0gw=", - "hash": "26014910633277885880116405732523760752779596685768035552816258944310550523123" + "data": "AADa5SMwKrGBTrX8v/BwTRWF7G4X1jwy4aH1XK0j3135D3ah6N/z6QWDlVfqRNhEJITgXakm76wXVSCxLoyWWo4SnEtAOUtLvbKpc4vPC0LAdjAPwQDSEDxFeBdkVPFu3ASgLJfyYVEgiiIGe460oOVcTyfYXg3BnQmPT9vtm2qLEcTKmRr194XrNKfjO2HZ/ScSx7YaG1tPtBEms+guYZ8aSXku+5hcOk604mHPgvfdUwuxTQ5716TJkEeVMDtoYSeq5gBjg3192Mp2nmI+y7whnRqsTOxMHEx3KPPb6xkgH+QV/0rfDOMw7QwhoFJlsRaS0brwbeyhQTe29c9HCfAEnhmihu8pQ6HJXILCKZ77A7jXDrcOeWWtm6baEx/NCDdt6ddy2JJ6lUo1TOqajqqkx4Yc9592FVfUvZq4vpn/C2coFBr6wnd1I71mTFtV/KHmuIWt2GJKvRUAMM1iPxQ7GbWEubyCa5RcnrOis2gNelbHBVE8KDIZsZME+caRTQc6HGmlTNcJvHpdNxgqzlqPuw6Uzporh/du6mCbhnqOClonZqvMQUMzoSekaBV4u0USqzrGlbHDitdKS9DqtjECAFio63bliiX7OoDzKIWVDamLiznodi+qRgl0GdLkYA4bFCIKrrBsYYz3oXMiysyeKMdt2kbNhs6RiBGXtUdFLh8a5E6ZT2SuhkAPHVNQA/oD3fNjw/ZgHZNGApokUVF2LTs2PY/EV0zscncaaOl3g7cdiRRDVeD6uDZXa3BUD1U3JWiiWNR8h07xvnqLRCYHpOAM/LOBIyvPSCczTKXlPw0hTRgmo7Q9RHKxwaJiyji+hyYau/eGM06psQ7rgPrSHMwKyP1JZYBCuWEjhRLB8cq4AsKUatln7SgCADX6JgAqx20bPiXnXgVdLdSDHmGW6iiCjn2F1i13YlExkQEWUBEdrUuCxG17IHUNHTrJBJC9E8Bv8Wrnb9w1RntzAsyVKb+C5aOETWraiQUVSPd2YYVfOOA/J0qS1ZaHp5lCImIggoP/Hkw6gwLhTWSgIvXrgMij9c2vyBWj8o+JiddTcBi6Sb8R8ruC8klmAp7CvB5bEXvMhiAuBbnmQf1a9/pXBJkLCknfojlXsOsfR+qWRvIsWUKx+RNSQEdzrmvAdKkgzhiOCBWUEb07rWOZikWdqNA940cSYb1sgDMDgBOYFzRkV8ohDpiMBVnjFBZpeRQyn0+k9qVzX9HqJDMWHVseBRmwOjhCLRExnIvvb5jRylIpM/Rh+ytUvabMk7gQqUolSdL7xHURXGSXIeciNI9esPKRJORsjBpIPf3brBIMCRVP7kqQ7AP0ghE3ANRioNDzf6B0eBwWvlHzv55TpvkGGsb0kdMh3bhXMzd07PTySe1yT4EoM3POGZUs/iOmwgYczXTfy/5Lx9ja5fi5easuouX8wTnCLrThTWGk0t6oFzBxkqXvuZFG+zpm9Nyhe4nYP3omOMPoDlzZA0pQJ0k9CVh08W8uA8/JJgMUDXXHkQRY8S08k5rSTK6zqeLuZuYCLoQaufiqu25robNF8iypUZpwup4V9TU+OYx24HTRrxOOHg5Oq3YaVsPCKz/IYaVFUmDrTl0xmgvtDNz1vVj3FnyoI64mhEAT9GSaVfpmzYb1MxFyuqYR5GMw1rvtNOwmTDbS5RwuzTdBPZ0yr9bg+I3IMw1ObfndBZsG3YhizSg1uol7bCGF19EFxy1LdPIzBkDxQWJIGCkYvy9OTcexCxUa9sTjfeviIGaVG/dTGRO0rlK+X5tSO6BaUsqyk+8Hd2iLjptEcD9fqJfiSUZqFm5JTGZ+1NaJOTb2JFZgWgJeurFg3n4Dbje8lIUWTm1R7idGL7rgep/0EiWivuA/DgAXmDTPfFoLsoNhdK/FPaUUEqdzW4uUAI73mzdrpgRyM3toDuOgCj8ETRtHofhTnTFzoOqpKATA4LHw1W4LqtEN0oO/0vCNukL544xjAX6jUMyXH+l9eB+uNNaVlX45TiyOhY0HnuX8vGdnv86ZERkQ5LPlz3lvOBDppAmsBcN7N7ukGHOWjPpa6Bn5l4br6Jx3Nxct8u0YSTpp6ku5hKoOZ6CWxoBa20DpkZtTn7HbWRnrVImanmOBv0mUEj7gTCKC+/2gr8SiZL3tjXFYorvQu85b5ovxkdmzw7hmLUAoO0OFcfvbsl6jXPJ3dCDU04h8Z4Oqw/COGRGo3ST2ZTIZv7vU1Kbv+Ftid9XK6/yPVDbrLuaCtmamWvDygYsSaBVlNO1Thd/ZNwM1SapiTr7M7sxpknl6nU/aA3eM7jAxF/sSSSN+MjE3nRE2BfD39ffy/3xNaeJ5rCSjBBRUC3QAk8ntuWRqgjFoj4Ao02ArLDNqdIUiAp9ok8xWazkOOh4=", + "hash": "5291064604824503627520574577442091057522908085604720950457286926675911858192" } }, "Dex": { - "digest": "2039439604f1b4a88563bd34178bb380c0d998cc662b8fe9b9ea1164eefe70c7", + "digest": "3456d7534e1fe092548a51418658e1c40e45c9f9eb94ea396e53e325002c37e", "methods": { "supplyLiquidityBase": { - "rows": 3752, - "digest": "8d00a233c53340e6ef8898bb40ad919c" + "rows": 3734, + "digest": "bd5a29fea5110fbd1b32764a3df8a8d1" }, "swapX": { - "rows": 1986, - "digest": "23a2ddbc46117681d58c03a130cfee3f" + "rows": 1980, + "digest": "6c5ebea27c999008969038efe8176fb5" }, "swapY": { - "rows": 1986, - "digest": "c218b1e81ac390ba81678a5be89bc7a7" + "rows": 1980, + "digest": "86d42373dca1f47deb67876bede423ee" }, "burnLiquidity": { - "rows": 719, - "digest": "63d13fc14775e43e4f6dd6245b74d516" + "rows": 710, + "digest": "1dfbe30c921c06c4e0ae2c6642257436" }, "transfer": { - "rows": 1045, - "digest": "dd6f98961085734ff5ec578746db3a53" + "rows": 1036, + "digest": "d726a5cba6d09be2ee2493195154642f" } }, "verificationKey": { - "data": "AAAn6e1t6dmR6nhB5BpgRjDAquZiTshYDyjs17DkRTUQMOJPMAJWE8MBdybFeHIINuCU8gFYI6WVL7RfnhhENHQjoaXg+jOKxlRuYp86axcshCaI2Z6lp7avzuu5nOu7yyfN62zx0IfPZRfKSQHEKU21WrAcBApWvY2KhF3gusz3PhR7Dfq4ckf9H+xv+a4ubCIg8MkgDV7Wcb6IR1EFOhsZnXzL9FbAyWknFdt4IX7xDX9ewK/8JFem+/KaG/CeghvHgHbnLNUbaWqiz6C4Y61xTkOYrBwny/tlrIKkTEWqHQogyRdkffmhgQmzIHcKcciqeZgyxPzGc8V1lcn4NeIZOvzO8X/XqZafgygMG51oQ2S/5U9D9GFJuxLfblX0cj9Ayo6jSwMgw8t67+T9LK3RPP1cu5SVJHYNPUBDD8FOAyQLfdRQfnQ/JYB85QBkjL0deUGDz5VmIrhXYltYBhMvcbEd2gpnsRhvSZ5oxnD5haF9Mt/2FNpBuLqKkj9cggkyp3/m8AFRQ9DPLArTuItYf9oqhMKh7QY/gvmcwjiVGS+k7UHapV8cThpM3qZ59euRVOw/enq6L0W0imM05T8FAA13NNkX/UmdYXLR4sL3zfzre5lmjB9eKvYsQzriv7sK8r1Xpl7XA3IGNF7USwJWREv0J1XCLVhPCnIgdOnZVA0kpZftbFDRTuxoYv1srKY2phTvz7kECsR4aTBOX4MiJv7UEqqrAVqfhXuVKGlySS5SONO+jkHlETUQxgEAZIYPGUfWhNRYxk3QOB1ale7iewzgE7wO3EjdmFOd5PvzYh1pSt4Wv+x/X4JZsc20x1YZE1xMPa9kpqw4+Wb1cuAtDfqPwGajHhIlzi8Y9FOFlaT9Hz0FKkUSpYj7LW0xJuQD0vLk0QDAqAXJQCIw+36tmPzdjET6WyjgFjnq411fJDAZHrz7TPYwxD59J8x369fkoxW/GWapHW47ruTFeBm0Pdy4PjEHRQASoMHcr1BYKdC2/+Ed7goH0RO/fq0eL/Ew6d/QLeiHdr3j1GMt8lUTByKBtP8cSv2ptlsjTWB2jRllpp5rhtBxX31q0XcitE9cfnSEwPBXDWRTQNtU1ru3GnYuq5VsQJU0K89lG1+XVnY8VYdNujjI1Yl0UzqKfLISq2QwHUCFO/dDCixOlIsygyTnyd9sJviw0xwHdp8zUD+/t4MIGAFZ5e0tF6AQ1xQ8d1fgpgURg2QoL08+EZxsJBxC0A3sGHVLMGZllmUWav6M0zxxyZiHjq9zdlJVDOMo/6YzlrVuRkbU8T+V9O0EEUsZ00Re11JHsGraKHSp4jQxVylFOnKwq28ieBF16zabCo/rtQ5Uo6xxMkPDVLJlJs9xTpv/T8Qm4rFW6fS2hq4uk25PZF3qk2DE11yPFPcEwJv0bVAsmjclkVNyN9CTnfuB8/n4Q+0A9zC78fpGrA3QL3fExFGsfQS2sFp30QX8VYOnp569qGGbTuzAG386OlvhlI38Of2CtVkDlYGiIJlDblAE2P6pwOuAjv1doKsxS6VmgoWQ/Y3BaRCFSkm7OCU07KwSwYgRrm81zsqsECanXo7qHIAmmEK8NmoSPcLWmEGOfgTa9AFoEXNyxe8EC9AZFTxyMsc0oSuyM1vQL15XwpJw7xtDHjhs2ICcORAOC996oR92WmNhq1dxG+5AUdEJI/riuI5dSYPap+L60SquwA81Ii7uZEP558TiRG7l6K9J0B9/wWoDVLp2Dzg6OVTsOzShFIFjB/JDaEWHPAadur8h8RLulqHG34WLVuQc3PS7yzDC98wJeEaZNOPdW8nguOAfFhdzcrMLf/VHSyeAb0JrbVS2ahgJXZi1o1xebypTCZMdEu5ViaexTkE2EADMEiwMHAbJYEEkurltgE4txTUGXQlEKGrTjd+XIcytAugvBNXcLIhsoS0RvY8Q6OWtu3sooqbVvA2INhXHP+8TwWxUqoCIZEQ2rM9JXVVqiNkDNxlWk69yC2v8wdSHmCdYiWzN7wn2yvy67/g4OhnccSf6HgiDUz3UEGNz4ON2OtH1lzmdH1llZpbO8Kt/mRLP5tstYzZecd9cLbN525geWWzo00yPr0+QCRkdErW3bsZfzr3xPlTHT8OoelKIXRVFRFNeFqzBlumbnJNY4cqdzRmPokn/SdLasdCoCrOiGhCXSIix3F5zlPvX4l1K6jf1VFaTF9eR/PrtZf/kdDQR4LFWb6Ny+4eatmHXAdBYdBZG6cAGmw3+8NWQOFqWqxyI+UYYdciQI1Q2dzF50JPTcfVBORG6gsge8EPHMOi4IygI3S2pLHVtUuyt3MhjBUqYm6EkeX39Q+qA1702y54+tbh/e5pC4FVpxg8Z85HaxTJJPDbdvVfSLpWI67hSHyA=", - "hash": "15086103482217764849797385791989057539041480977574988157913770221410426744249" + "data": "AAA9O01G2mHb1HXzIKMMfQq5Eo5VlnpVPEAGNfuMZqLAHB4vTHo//MOFrqHIftqgohqh8+HJJP5kO0/5xw6MfQoF7hJRnShezS2qiQkLtjUjDyYE1aV1/JN30RnzN6AcQSwKIJiFaPzXIpyG84ZO67qujtL6p2agct4jiVQwS6Y3CFv0f8bH3uVUAer6B1gPwp4CTc7/za8e29UW/TuAwaEVMeXXenbxqZqTXGlDeHsgRuNKLNtFqBAoI+Fc+yGsDhUeHivSaNUAIOfG9UwzkN8uCj6P5XMtwipiNVFizLPSDB8acOV+599xRhceQNKDsH7YWub4ntua9ooRMWSjOW43H2ln+uRYwsTLzopGH8kk8mZjyYf6FFNkkGRVC3Bl/hNBf5Fx06kGOO+B+l2TtwN4ubYuu/ICayUBsAsNwJWDBidufV3tDjH53Fk09XfoGVeUG+CViJ+iTd1t0NH4c1EMuDwjhoPm7ASR7jftvWqTtcZm80lARkzhx9HHMEfoBgo5jZk5PERX/FwSGcXG2N5paygiY3NaVRLzsgwKtzmVJDwy1rptoVy+o4/0cIeXf90QanmK82oLfvcdMkHiEuYvAJ3XLJYtn5tfjh8GO88gKwJPkCPdK7Pru0yMl9ssj7kC+/LAmgYVkmwMDuzAreM5XZhcRvL4DWMCKyvOR+P1jhetE3Q1dPUTvi+6HMLXF4DkcB/oSSCo7czJ4U2DC707Nlh3layFaxE0daWrHDSCuB0fPNx7hE1OvF8c782OCtUVdmwyGmOfvDOK2KZosWHyhBzUSpKZRFyIjYjcIEtIzwxx7O5oPRe0FVPr9mJs6rQwuGJMhpLiN3NNqYlgAIv1GsgaCbSDehSqc5uh+GvBLexoeyS+kaqCYCajrFHVch4ztoJNB8Zs7X04nLmmXyx+DTFU9drGRIQGeRoiiEDOzTPfj2EA1KSQwrqUrpCLvVMw3ZM44+3YJl8er6UMOm34JAqD56LRlWbEJK+Merdd2AxIGxn65yZIRAt+65H2gBMRI0volv0m9NiaQc7I01VdBR69zlFg4oMQXgpmBAWyhikPq0AAvp4Gl5ot4gPB5HSywWQloEBUhIrThqjYVSqJEFObRpbEWVZBx+grlJCaa5sLAdE66aJREqeA4SCWg0Agcd78JyKaLepZc0JKKjVg4EZzBg/iV2PgpXnryOBhJSvpnp3qJQlRl4JiVCuoyW12LROymQ3vYgb5eJEyEO9ZPgNqfWpE06S9UyVZW62JbkdYkMlJ5Q9vH3TR+pacwZkN4z/Ub7/4vHYvWlu62FPT3n83vXKXV+pwPgE+OTGTQgJbAWaMYESvhfasouvh4u+WzBgSG6ibuazjxjToH7KuDKVfiA5j1EHTTojwIZsqJqA+pfPBFCOO+59n0Nka9Bsjb1jR4FTiiSZTGe1QFmzqm0UfXzESRqNN4LqXVmXF0wxtFm6HJShg5duYOI6OZ1n+aYW2uN9I9stT92LqYy0YN0ArG2IL8rQhcmEXjvMSclijeHhePXw3eK+k39zq3X8v6orx/l2Wk8NVgEmAHKkLlZgmYlwkk70//AqgmrYEBB8g8hXtFw7ta8Vr02Q8kuLpalIrz5PTnW6VBWVaOzjvGFCQgOc6Af52TNyEFAjxKC76boB5iLLODItC9D2pMVgqi1LRTDkwmADYaQa1V5BYSu/LIPNskPwxah4Tdn0CaB3wBc+2bfhUHmEopZ1MSDbU4nroVAfmZOaGLfNZDZ7bKkVDJuQabWFRZ4iavxAn3MdrvGL/YdDIlK7Gv4yXY102vOmDlPnj8Cu4lZTxrh9lCyqv4PjMnl1V943ywqexTT5qiH7IyUdt7Rm3cGfImUTiJjZ///XTcO6FoAciGsHfBABLT34A7mW7q0tOpyzhFf4hMAc+4T6jPEWtnb+wmhz4D7R+xu7V3cZg4lqAsoswEXmE9LzC6iB6msdXb5J0EgoLfR8ge0RwpydwmwvP0k69BK2C8LI0MOL8kV2pq0gumypOOx7Y1VyaGKshrxHp9lL6Ulojyb6YKSlB7favvBSbInYhurBiu4PXUpKVmxqxssEvaTTHde8+/b3JK4dfWAYlOGy3X7DWuYXVR3XNDs/01gaeUEYi4XeDnwEriPzYNgFUXHDIkeeHjIhSxPNULo72dUq2G06JrFh1g6rer9LKIZwG6CU6+PkPkXDPJl06tNDLTkQciRzICdMiY+gxP5c53gDbI9kbrjjIDf5gVbaUvxXehdE9j+Kv30WDxLW7zAMAg1JGxmbNIWuV3Iq5XMk+VphIGqXMJ9spQ3HFoxwBFPJlgZCIakHuY9FVEEefW7azD8umP0uueu0bfk43lXM6ORT0A2sDW0KC9X6eloIodgnsICT4iwIT8PPLsTI3WjM=", + "hash": "11459004586131119833485441687994116008945905628818250750148253447579283694397" } }, "Group Primitive": { From ca2959b82cd2e6d461365b8bfb0a303a6f09932c Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 11 Oct 2023 12:47:41 +0200 Subject: [PATCH 0156/1215] add Field.sizeInBits() --- src/lib/field.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 8b7bf25df1..16e79c802b 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1256,13 +1256,24 @@ class Field { /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. * - * As all {@link Field} elements have 31 bits, this function returns 31. + * As all {@link Field} elements have 31 bytes, this function returns 31. * * @return The size of a {@link Field} element - 31. */ static sizeInBytes() { return Fp.sizeInBytes(); } + + /** + * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. + * + * As all {@link Field} elements have 255 bits, this function returns 255. + * + * @return The size of a {@link Field} element in bits - 255. + */ + static sizeInBits() { + return Fp.sizeInBits; + } } const FieldBinable = defineBinable({ From c282ecf0676d5f0f3cc3e5aab7d960d51c63610f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 15:30:08 +0200 Subject: [PATCH 0157/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 014c1f245e..256e160fcb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 014c1f245ed620c3c1e4a939aa36a728ad148cc3 +Subproject commit 256e160fcb97d93f31bd559bda9cfd31fc1631dc From f34dcff0c6e69c04779c773cace4a52bc9b00d5a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 11 Oct 2023 17:26:14 +0200 Subject: [PATCH 0158/1215] update bindings, vks, changelog --- CHANGELOG.md | 2 +- src/bindings | 2 +- src/examples/regression_test.json | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf57ec96e9..65943f2a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes -- Constraint optimizations in core Field methods cause breaking changes to most verification keys https://github.com/o1-labs/o1js/pull/1171 +- Constraint optimizations in Field methods and core crypto changes break all verification keys https://github.com/o1-labs/o1js/pull/1171 https://github.com/o1-labs/o1js/pull/1178 ### Added diff --git a/src/bindings b/src/bindings index 256e160fcb..046712cc50 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 256e160fcb97d93f31bd559bda9cfd31fc1631dc +Subproject commit 046712cc50e59a6c31e5f63952fee132baa145b0 diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index b761173879..e492f2d163 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -24,8 +24,8 @@ } }, "verificationKey": { - "data": "AAAmF+ylfZXG6glBTCjrbm2BTEp6JMdPJQu1cfN7LMLMG9gk6jRP1pnY7DE9mENmLsuIDJGf/d67ouH6IaWle40ErmhSboXVyvw0w875YFdkyY4/ZiZNJatYwjozv5zroxM/b3ZVe/SIImxhtJnw74M3gcVufpHb7CFWx4KTBUxmF6PBw90hLKLjZ6EcSWJTwyBp9eQfr0FR4R3JY0BOd7w3NawTRi679tCO4DqW3ruFEWdhNB6q6yp61vWoByfeJSck8OFDjtOio45p8mab1wfBeFLe0vL6/xGfJ57cN5JvINvbMju8/+7a8Z7cAA1A3s+B3h0E2vdNMDHQknd8SOUzoMa4wmOjSanjlVVB5BjmP/eAKtTq5wnNBAF+vRLdrQyyIBNRGwm9YgntSZaV0lz2eGtrlMyYsBr7zQDo2VAiHNadlEKh+9thtnp8BkjqQF3vY5UDZ5KH8+mKB00hyj0ezr9Zsky7PZv9rE5YiLQI68ZgUYZQVq75QdjjQ3UsrDaB8NcjuobySjvCruRBENKU2bPDz5UdYIoVTifTqBMYLTx1LnaMFMV8ygOOL4cwxbRPJVZAkxF+u5GCarccbrweAJqT4hgfpk6wzp+igGGh2kDyMG0J2/zl7dZAzJF2kCwmSnYqFZ9hRNxAlBCUKwwv6r1Rv3sb4O3SNCsptIRemChbpMv67pPfR5K5ES7xaLtI862mA1qNuJYFdlTW8u2RBIff+eATjEM/C+DEJ16rqDb5I9nEvd0ludXIJa/PAdEmGfzWClrrpoGkN8DM2YA9CFxh7fePCuAvkw+eqqqrohGiaZv8ImYGkl6+GAk6GzUfAWBWWZdxLB5lQdeUJwfXOTPbGc5fH+8UK8eRCjrcHVhQFvDWcqagh3k4rPVZH4YzlEOIbMacKIbtLLxMX9NzMbHamvCZVI5NVluJ1W4Fxy+oPjZSRBAIuvhVXtIpiACJ2L/Ca9ALZsOxQ4FoINWkBtjMqaY9vcqBvY82KOzUncdZlpIVTYkqmlJKiePsEgMuJR4dhWi+pHOXfZYnL3lQ4GifqpMjIzRlBxIAmvi2UDEHiig55p/1LqdOp2O7ElZIYxDAZ4grvr7oBML0kFfpL9pbZ07ZSXNMA8wyUkHx2OFhzm7AaBt1cco8hFXYMds+afqlH6Tu/83Y/zw/n8Q+ImPQ8oXxklISuhHk0IBnXyQhnNsVLHOnVLHKBREpSdssFcDj24k5R7Cn9/8oQA0nEIrml++bB357GnzIZMkXy5RHUAg9EeKH6GwkX30EftI/HxA3eT5/G+Ly+W5tIh0G8YuZjEdsY0MbaD44/P5iajfftJelHSXI6RJ8+C3UXNkjDfNE1ayOoGqE8Gbnw242I9Uc1xdapNmvjxjnWRIHTD58VP1CpbQiaBw3cJBi0GwFDtggo2s502bdfQwsK3RK+tstKlDGDrPM9hYGDeRNPSmcNbMM2qdIyPTUkMqTwZr09JnLyOR/Yls4LZjQ1afrG13WAZVrp9o1GgtE4SDZ+DVZ+2xRvrXO1p2C7Tp0/qcJmqcImhVyBo8PwdODdvaP47s1YbegUvDrwU/BuO06ZBxRgHZU+anO6pzZiVeHsOlpulOXXAnvPWlJbMyF2cAOCWeNCzkles550yp5Alw7Fjd/kRiwefjqTDrOaAbUOYQjyAB7isaEm4nBtwNWeHIChZjI3ezgZmHYuvC6OT6g6AQWZGXjMNlPR5xQMN3e9hg4mgE4CLUDgzSJCkGIhc1aBdd+IlLqwG3e5mT5SDtqS2vyL10v/2Bq6KRIU53ViRwN8olYKsgSQaeY3xdoTS7LVoRX40N/9T2N6yjIFwuqyRq4Kvm/TY9+AfVy2TFg8Y/ZmXBdWLAwN2wO8mRTkZS1LwAuIpPCOIlRcvelI75+5bNexLtzflOUjDQ+Zlzeg/61IagGdRIIdmBol1qVQUZIXeLmFEcztSOaqjRPTuhYOQYdPovz3ORH1b7VZo/uPPmm1nAl0Hsy7jRwXuCZG8gOJzRuZsKGc1ExdYqz6f9VckVly8Zg4iDWTr4FrIyi86YoAwh5slrh45xpLnHGq19qiU1PxMt6Ekv9TP3qdcXJxJ0xQHkENLvQMQEY4Pwf/+iJJbv2IGYDqA2osSJDMA+jmCw8ivkJKuivI7j93K0tB8cXUDK8Zc+A7L7pZ0YEr3nwIhscSE13iaauKRnKunVrYU4kYoXTNSeKUOBLjlyFKmoH2/RMqhXuzbbC7D9gYaGx5pNip0mFIy8Cvegk1048IS1f6ow+xlQLkUZKCwQWkATl4GltQtPE8PsmY9PrPnG+KKDS+GMJIXXL9+2Jgj90hqwZsF7Axp6gjpM+Y4FkFqksGdZ2JsLrcFhbeSE1/vMn7hCznbthYwOZ3Tjalve52Tk=", - "hash": "8177143440582746915916548070325772209123983947365759992913134980137125856622" + "data": "AACd9tWcrEA7+0z2zM4uOSwj5GdeIBIROoVsS/yRuSRjKmnpZwY33yiryBLa9HQWpeZDSJI5y91gKJ9g5atltQApAhMdOuU5+NrHN3RCJtswX+WPvwaHJnihtSy2FcJPyghvBVTi2i7dtWIPQLVDIzC5ARu8f8H9JWjzjVVYE/rQLruuq2qUsCrqdVsdRaw+6OjIFeAXS6mzvrVv5iYGslg5CV5mgLBg3xC408jZJ0pe8ua2mcIEDMGEdSR/+VuhPQaqxZTJPBVhazVc1P9gRyS26SdOohL85UmEc4duqlJOOlXOFuwOT6dvoiUcdQtzuPp1pzA/LHueqm9yQG9mlT0Df8uY/A+rwM4l/ypTP/o0+5GCM9jJf9bl/z0DpGWheCJY+LZbIGeBUOpg0Gx1+KZsD9ivWJ0vxNz8zKcAS1i3FgntjqyfY+62jfTR8PW1Y4wdaFan6jSxaaH6WYnvccAo2QHxEAFL91CfnZB1pwF8NAT395N/rXr5XhMHFPoCkSHd2+5u+b62pkvFqqZZ9r24SMQOe9Bl2ZfMew2DyFLMPzwTowHw8onMEXcVKabFs9zQVp66AMf/wlipirNztdguAEgTiVyzydYxNTKRpau/O5JaThaBCqePJzujSXLd5uIguUQkKMjVpfnHKOeoOtlMY8PYYFASPZjP4K1Y1XpE5DIc4d5ts+btlepIrTet7yJK5rlsFQfJGzaeTz9BN+g+C2ZK8B+2a2Qrz386FvB+elJAkJ2/Agn35oBHB2HobDkF6sRfrXOdH5l+QV7vR2v385RKRtfnmcJeUQcpq5/JTgVwagDJ/FarTN5jFsrBBRTeW3yZ5/CfVNA7NNWxoKhjBaHVIhn/fLT5sFLYzYdCx/uTsusyZmE2d6iqnLS+j1IXNJX/zR0ZD3aGuoUc4MaFZQnN5om4dfpbloe4Roob3BuDhBHTKoYC+nVsyEvDRyiYLEOjJ45/bSwTCfwngYKtNmo3sVTvQ9mqBf0cLdBCn8skp3S/gz324TFm8iJ+t8EWVKjlhM+1lrOQC7OfL98Sy0lD9j349LjxKcpiLTM7xxR/fSS4Yv9QXnEZxDigYQO7N+8yMm6PfgqtNLa4gTlCOq1tWaRtaZtq24x+SyOo5P8EXWYuvsV/qMMPNmhoTq85lDI+iwlA1xDTYyFHBdUe/zfoe5Znk7Ej3dQt+wVKtRgMqH5O4Df/c6DNekL1d6QYnjO0/3LMvY/f/y1+b7nPHI8+1Wqp5jZH8UsuN63SSMdfBEe6x46AG/R+YS/wH78GKekabWu9QQnUJdjXyXiqF4qRebvfcmpQz91anvVz3ggBqCv4sYqCIvP0ysDtMdi36zFErV+8SdUu+NsPDGvdPSCGdLuC25izxb21up2HORmlM5R7yuIW3rCiq8DeLD0OHjqOBZ+IEv9zEkb5fHTJvxoxnZlArtZSBpD6iIDPVDymuK+BsOggZav3K+TytjeD2Gcld5NfyRISFWUIMkZNFQRL8AQpET6RJnG1HSW0CaRfNeomtjCBWIr85wFCrp06j/D1J8B3EyhloZLJJ6ywxt41smXVugxA8LRTO+6lVBOBF14jHQCCUl6u7uiWCe1z4/bC5wQXPwWSljp8NVU8Erp1U9ModNK7W63Pkh0efvgSD5d0nLzbfa0jTdxZ1JkfKsnvYk43Ed+vmXooHZhUeZAIX8ZCizhb1Gfvm02JFwxYXmiYAOp5wkGzweU2I5zo8r5yZFI1r4XibNQs7eAfKGRv3gh8/EuLkX/bdettgPvNsI8ndpQ3kL/V8W2PQN4/hjC9AKCYBeXQG42bRncYZdLe++R2KA1ZdPDxQPF3sxUIKhzmRWqbozrtv310Maorwv6eZJjldlCJwICR9QgcDwDuNj+UFJnX3RWsdIWsUbI1T4wO0sE2sBiMX/OqmiGJEAnBegioistlFyfRvm54h+duNOl/ol1Fva7NoXvsL/wThAWUly7bnc7/Al2bBQlUrmEX46UnKXzYntkZDee7Lx1u1BBkJAj/5BH1YZOPmMCh498rBUiHmc+4uQqebqNSHdOSgC39ESss4u7GNhWj3fi9XXta6UT9wapEMGq0WTg2Kry6xNP2YZ5X8eaapRQc/KzYgz9XjQL6TKpqNuGEbRlmfYvIuoFbnOkZI7RYoGp3YheMs1pQErwOxLzZa9W3Okwx16TSDwPLR0xMdAyogMrOdKN4JSMyNnmOaoVf6PkN+K9fz7RuHtvgjKpuz4vsK5Z2wRneqPrnfu6PkgHcRQrd0SxqCbN23Z/yp8qOcN6XU49iCNEBjztT00tolQ9hCPMSE/eTZ+ioez7m3pJFVks3T5Rk/e+6MeowJWIOv20x6CPS9mhpr1JPwdNFrWdgs19VsobntCpF/rWxksdrYyk=", + "hash": "1740450553572902301764143810281331039416167348454304895395553400061364101079" } }, "Membership_": { @@ -45,8 +45,8 @@ } }, "verificationKey": { - "data": "AABoEASTjb57lVvHHkX/S1bbJFKReCgeS+fs0hEX41xWFGi+iz2GxWLP9qoCdBFbkaueRy33tn6JgWe/uOx0GlYX99RUUltDLh2wnqOckf19P3Yu4My0BPcyY6lArTlZmxYgN+p4jAg1RqeKBKB6vwjA5yG0qxMA32a9iwk1t8BeBuR4E/oS3QgckSt7mmNgqvSMa9IrlMe4eXxw/+blx6sH2gxtSAbXp2gevWZq8p5V5kSvGWI3Nlj+mhKGEexdcQc3VDHQG09gPMYfVuOZmqjlKla9A8k14U1pxT2wad9pJtbTmsY1O5NrfLeYY6oQsd6QMM1bmg7DWdOCFmcsyqUqL/0rV8Elo449eZoXfAHKMX/0tJN41vEsVQKrUJnzGDiv9QhkzHRPCano4ntvPPhT97FQIJ4Ug1w0GQ3AGssYIC0URVUhydPc5zU2+DkIhT2ROrnFzKFrKeNvkJmXjEYhukdWjKPx0fDjk1YqVHlEBv5RkXNo+8TY4p4XnwrYjALifnQ527mBj4rW+LUTPPZqdj+rCLYNORVFCv9ca5YMBD8ZwLE5XxfrDhXBBH1wFJwqkhYWNiHRAiCiYPa4Bv0LAHBN33WimVUmN+R2VRsLzrao7eUgSmteVx4kc7TaDxcHcWHLuMzHzLo8R+xUTFvSU+EpDUvxh1C4qIyZlnDESjGmabXKYf5/sMgaKb/gCu2oH0vfPw5+SZeRQt4hMzRfIqoSO11gk5ID/ILDoKESP87kuXYGTq9zc4oMAFgtTH4aWLmZ2H6ZhP14MLBPUDnW4FnvR3SCxmg9VW/WWLK+NTxr07zq4HHlFs1MG4/2G4VNwJN/t98eLL6eqxGye2I/OshpizJhaxOhnLJpFwWcjf3I32fIdurv1htpWhXGVcAmbpzwXh/s7wvuCEvdpabhEFOgQyMfhGe+kCy57Cak9hSSYVoEDMBd5hjkl7kZvyajLzc7Tms0lpHok7XpEZEZMg+MZtbv4mGkCtkkyFp2EZh70tUvMG5OCFcqzyUGvCoWkEaAa2qT/FbtPtHrSW5neU7f58+pTv63GT9l8j43pRp9b9gyWG74hGB/fRBn12SB7uTVUui88gr9h2KpJCeKOFy/9SJSBMk3viZJKSshKv/HIOlC3ywb1yTIGrYtUWA0d4s4RY9uaC1IpTwGf50k4WV8uqfb+llVjobXQdxiRxwxjoqv4df4DHJImLcumThOGx/6u+yn4E+gV/F9ydqWGy2qztI0icDm+pRUxADA1Hrxhl8pA1hMEJzxTbi3eIEt/tlnsN8WNeXwiuCv1Ey3+QjAEIZwRYOAfvMe3k4KMSCSD3SDqewZwq9D8RUCOSdNsDLOKvBZmdKdjE+TTQUuHBcc+fRpDocNmgROwc8Yp0N1tTQLUvFvZoLYwdlBkM0gqZZafKAexrc9LKIYifw6z8WlD0b7dSBLH1g2151SNAKYpXZcsYLuZAyRH2tNpncaF69wGqmUjoMV0MYztDXGFoVk01oamPCiUtBbFk5yqTHsYY5K7shfvW1X50SL3po4CuJh7LRbhfz52ukdfvI0NSC0QWlztJDaMVPPi+biXTwwiHr9bLB5YTZe2SD3cMXKAdq1LY1A4OY3DH93VbcvId4pRb0IefVhmQsihMqW3BrkvU8IVBQGkt8I/wCWCdsSSnq8vOjfJto89AL6dK+Q2eE/OMF2r3mcI53ErA2RLh6ECLQvFI8w6rBBuXd8d9acMfaRdD1ce2fy59/rDtNjBLQkZ97QQbAQv8WZs7oraJ5ap8cYRsh39UwwU60KMDk4FFxOBwPgk4XYm1Hvk+LWDm6M5drSe8wHfa9XePoxvC+5xX8UynCMvCXDGgadVpUSgBfIY4/HW2lwgtWm0YbNJACmcfzB5CV6aGEWaHe6+JDAV4HHDGjdRXAvoX4anzfHOgwNPt9zVCbDpJ+Tn0ukgwQlVbLi3i8ZY1WomNOghT8OLobJDArhQYN750/y/oDl4mdYtM1waD1RKLU+Rg6ZtxIFDbhNVeWrffiixWwr+vCai/3NzNuNYaJ8F+ljeu4cBM45PHDoHk+GYmNiMcyiWBqtoSh+Z6TJLAqKdbJM1/MyxT4iyXVMLilrgYwzkO+iEKlHlxyRENTcN8Fh4y7t4CevdShnlxZzMe8oLezOHqaTS5chpQcil7FUuoO+DuvLF3JJoFG4Uz4dGv3HbiZfyp2KDw8Wrxt8xXs0VKmkJK0bWAIOXuCbprcwBTc5UcXRso/7qMATamgk1qdmzeZE0ihQhjhjSNQUQTdVTQU2ZQyPxBY/Bd3YZgRr0aXFJcJkNp92anL09yaNAxwWAwx9aARstBYydWv4/XMf8LJ9rgg3DrsF5tylpWMKKt/15onN4HqcjsKHuCnbvBFhVJXpSx4=", - "hash": "19885179824976145877503763682456217863981026202211267110837478316889179989264" + "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuAHHgQqvSF2AEzSEy6kDop6fnFtVTxzp0MgW0M9X0uVcRTRJTkcVZSz1JzihGEjzkEZnZW6tVr6CEkmzXh/t3DSq2vXswFt90jphf6jgLtFJULrvKVg+YCMNM/04QLTGcMmjjzv4LciQ6IVXth7zhVKxfL1/2peC0r/ZrP8k+Ox4LEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4IxcktsWwr3mRVBRM4iPa87OEKZOxq0wWPrGcTnmqV/ihFAcp38VS2KUNwsiWjprCq1MFDCf1dT4c1U6/mdLP6AI/AJi7REoCfvJfwxSZYr2obhnskD1VjqelMdksHemFbsQDczNhNcSg1TTD5ZsuG71wj9rSJPEisRCRRd733MLARwv6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", + "hash": "16610506589527352533348678289715227768202510979537802187565243095524972136674" } }, "HelloWorld": { @@ -58,8 +58,8 @@ } }, "verificationKey": { - "data": "AACiaFuhyHZg+9tkZYEjZsmxuFQYVHg5vmVKk+tLT8H0F4d/diO+50RZFwmJgxMLdjtB0FC2dogj4lwETtRBVQ0Sogam3ZpuxQ/031vfhq4Ey4pJCZkVrSHd6VP0FU6AZxcRHU4NRlijSRMiQrEvpJcvgiYHUriVstRU+9n8BxpwDyjAYktbwctAi1+ClgFDnszJylwuRwMDjAH373ambXgAiavUCfe1dEPoaUOK0KTaW9Z4oV7AX5ZTLiNoZzIaQwvpWJdBYvJbK23+RNnY1E+NwQbTCQmSF4xJlmZo8CelMfjQLT8uCPLTDBI6yUzPnEd8E3ozgeI5J4LG1Kr2Zv8KnmCwX0iXDcz/kQeNLeeurTR0EXffCNUvh02o7DlhOSwIFUCuBrMPkbzii80P8P8Ez1CcOfituMgPbfUzKveIO9xR6vsul4AuF1ToAPRPCxBrAg1CZFH2zwwwni6pZdkyZSZD0ZziOZExeBYaHPTKWdZv64Fh6jbR8nfyhnZyjQuCURJBrZY1I/QIr3AWqSRAwEw+M7t9LrGFMru+ghcCMj2imRf35aFfRR1tWXUc4wWBVIf+Z/ZUAh3F7YmumbUuAMaNFA48N6++zHqGH2UyUsrZZdk+c1I/eyNQn1CqzHcB8p2L/uAs1sFR/WU40Nh8G+MUfplYMLMyAcNzGM94ki5M2F0EIWh9bsNrgyjXYBsJ3DziSuNvtnOQHIOXjZSeF68v3JbJcFLSp/NOhXGRlkukTWGhSl44WChYWXVtvbUcxO7XZ21kwsOvQqVr8LJarkTdMXUPe/aljXj8Maw9LhvFPLo3Mah5eAde+DPovjtp+hzGmz6dRxO0bXeuqhyGBmto+kSBBJJmZ0wRkcSHbF/WzJmO2fucm4NSsIGJZ8wDXmp8GC/4+z2uFtCxq26fukHB/gEPLh4vEmG7T2PYwBWid+rQE9WlQl2qyjU0RQ7WKHukK4gBqL1blHcFSqefC62Gtw/gbygRxIsAhBJd3oA5ZOgqW1uZSvHekI/S2CEV1jQhedJyKbcis18YR0dYYFxWskW/xUQYXrM9gpfKVxvWFzrMLfbHIAF/enEwJ3yPrkVNULqbbs7uXkm+58CQJCV/p0yM83vIcjGR3Ez9Ust2G1cjsxqEFtKpJeJ0kpYnzJ01dYmC3oFwFY87f2WKjlC9lASgZjpDNVGPydju6TCD0GUcw9DTvMxT8JXC3L+bfw8TVIRLbHL7vE27ksgUE/4bCRGl6CuEb45KbO6E2ltXcTIXiDMzXHqSO4ngC0UI8LC1Bcz0Eve5dlDzN0lfAlXvC4k3VqYJTrQzcSKpTjNkKt/3nP3stxVFHbRVbK1y3RvJcay7CvOgktx/JmYLBMeFU5fkrk2ET1pZjBhRA+oZ74A3uhwTf7Kt1f+gO342NDzBXuHovGTosUqkrRTCsAewDy1aj69jpc+60q+M+TNBT10yKsx4IKgIpMWLlbJRXAMFLe9AmnqdFpwJC8fFJ8kM0AEIhbiot0wMCITBlbsLzz5F1Y79aHtuHFiqaVYVtnlEuMIEwmuAyo6hTpEQ2e+bZ39/bM7xOOxIAwleVjYo9pA+KwGdcTAVPxObQIxlD8xgmfd7wzD8W/oLlE/jHRxdyoxV/gfEqcwSGq0lYXLTzwEqOhOcyhe6OhYhfsgZG4STsdGOpg9pLehvH/miAOPselsXHmFpyij/LQ2KlyeBug+lfWTctX9aZfPVcDnNpnZDuZC9abSNlW8ufBfuL0ATU1ySocbaZ8rlSVajn8KrZnojUKeq9b75ut33/ecFF7udlaY9tYpgFm8AT1oWtLGdoegiYXn0URpVhv2oSTCL/sSVcS8XaAUKpwRcx4LkRY7bmeDRYoXio5fG1/3DBAAmdPV09kmfq2qzpmxCNVMEWYfbAXbYRgtgOTkkne3MEuP4tn8IxqBIL5qRKrCSmERuFaHmO11BZYQgS70zpPAtBswP3c1z5mCvFfElFtczevprEMnFEwNFpt2GnliU+SaLyvhE31NuC40Jcz9uoq+FaMww1E6UNi26Wai44BbiLaMlP3YWh22VW2twtN+3nJyoonIn7+b3CVCCmthz+YE+/qVtgD5z9ZDMVp/ax1suL2ot51JcLnfdcv6UZL8R9zezROXCCFrEUtE5ojIIikOvxUesRYy9fu3JGFgrGHdZBDpTdaj5yhh8ABUG0ssLLMO5W5ru9cnSR9xTJCe8ThAj1028+l4JpZca1/zxuM+1aoFbPiLEIo9UIeQsOgMzPxK+aaNUINDZ5g+jdwNEfV0XoMANCcTrjcf0ehElT0LbA3Efn+qEmNBJvxk9UqVamGE2thwXeGbx0s1AAuTxEog45KjRNnOeDN+OGSi8tc3pq9OmRlcn4sW71cRb9rdCtxI=", - "hash": "9545701657516200907575671869486617924893012564683059283012062997525681112579" + "data": "AAAxHIvaXF+vRj2/+pyAfE6U29d1K5GmGbhiKR9lTC6LJ2o1ygGxXERl1oQh6DBxf/hDUD0HOeg/JajCp3V6b5wytil2mfx8v2DB5RuNQ7VxJWkha0TSnJJsOl0FxhjldBbOY3tUZzZxHpPhHOKHz/ZAXRYFIsf2x+7boXC0iPurETHN7j5IevHIgf2fSW8WgHZYn83hpVI33LBdN1pIbUc7oWAUQVmmgp04jRqTCYK1oNg+Y9DeIuT4EVbp/yN7eS7Ay8ahic2sSAZvtn08MdRyk/jm2cLlJbeAAad6Xyz/H9l7JrkbVwDMMPxvHVHs27tNoJCzIlrRzB7pg3ju9aQOu4h3thDr+WSgFQWKvcRPeL7f3TFjIr8WZ2457RgMcTwXwORKbqJCcyKVNOE+FlNwVkOKER+WIpC0OlgGuayPFwQQkbb91jaRlJvahfwkbF2+AJmDnavmNpop9T+/Xak1adXIrsRPeOjC+qIKxIbGimoMOoYzYlevKA80LnJ7HC0IxR+yNLvoSYxDDPNRD+OCCxk5lM2h8IDUiCNWH4FZNJ+doiigKjyZlu/xZ7jHcX7qibu/32KFTX85DPSkQM8dADbWQUmeiyX6c8BmLNrtM9m7dAj8BktFxGV9DpdhbakQltUbxbGrb3EcZ+43YFE/yWa3/WAQL81kbrXD0yjFthEKR89XcqLS/NP7lwCEej/L8q8R7sKGMCXmgFYluWH4JBSPDgvMxScfjFS33oBNb7po8cLnAORzohXoYTSgztklD0mKn6EegLbkLtwwr9ObsLz3m7fp/3wkNWFRkY5xzSZN1VybbQbmpyQNCpxd/kdDsvlszqlowkyC8HnKbhnvE0Mrz3ZIk4vSs/UGBSXAoESFCFCPcTq11TCOhE5rumMJErv5LusDHJgrBtQUMibLU9A1YbF7SPDAR2QZd0yx3wbCC54QZ2t+mZ4s6RQndfRpndXoIZJgari62jHRccBnGpRmURHG20jukwW6RYDDED7OlvEzEhFlsXyViehlSn4Evb44z+VGilheD0D6v1paoVTv2A4m5ZVGEQOeoCQELABdoFrIZRrd4+glnXPz8Gy4nOI/rmGgnPa9fSK0N1zMKEexIlapLarEGI7ZVvg5jAqXDlXxVS3HRo/skxgt2LYm8wLIKLHX0ClznArLVLXkSX18cSoSsVMG3QCSsmH1Oh8xOGUbSHzawovjubcH7qWjIZoghZJ16QB1c0ryiAfHB48OHhs2p/JZWz8Dp7kfcPkeg2Of2NbupJlNVMLIH4IGWaPAscBRkZ+F4oLqOhJ5as7fAzzU8PQdeZi0YgssGDJVmNEHP61I16KZNcxQqR0EUVwhyMmYmpVjvtfhHi/6I3mfPS+FDxTuf4yaqVF0xg2V3ep/WYnnKPJIegxoTFY8pChjyow3PMfhAP5HOnXjHQ2Va9BFo4mfEQXvRzPmIRRVmlVsP8zA+xuHylyiww/Lercce7cq0YA5PtYS3ge9IDYwXckBUXb5ikD3alrrv5mvMu6itB7ix2f8lbiF9Fkmc4Bk2ycIWXJDCuBN+2sTFqzUeoT6xY8XWaOcnDvqOgSm/CCSv38umiOE2jEpsKYxhRc6W70UJkrzd3hr2DiSF1I2B+krpUVK1GeOdCLC5sl7YPzk+pF8183uI9wse6UTlqIiroKqsggzLBy/IjAfxS0BxFy5zywXqp+NogFkoTEJmR5MaqOkPfap+OsD1lGScY6+X4WW/HqCWrmA3ZTqDGngQMTGXLCtl6IS/cQpihS1NRbNqOtKTaCB9COQu0oz6RivBlywuaj3MKUdmbQ2gVDj+SGQItCNaXawyPSBjB9VT+68SoJVySQsYPCuEZCb0V/40n/a7RAbyrnNjP+2HwD7p27Pl1RSzqq35xiPdnycD1UeEPLpx/ON65mYCkn+KLQZmkqPio+vA2KmJngWTx+ol4rVFimGm76VT0xCFDsu2K0YX0yoLNH4u2XfmT9NR8gGfkVRCnnNjlbgHQmEwC75+GmEJ5DjD3d+s6IXTQ60MHvxbTHHlnfmPbgKn2SAI0uVoewKC9GyK6dSaboLw3C48jl0E2kyc+7umhCk3kEeWmt//GSjRNhoq+B+mynXiOtgFs/Am2v1TBjSb+6tcijsf5tFJmeGxlCjJnTdNWBkSHpMoo6OFkkpA6/FBAUHLSM7Yv8oYyd0GtwF5cCwQ6aRTbl9oG/mUn5Q92OnDMQcUjpgEho0Dcp2OqZyyxqQSPrbIIZZQrS2HkxBgjcfcSTuSHo7ONqlRjLUpO5yS95VLGXBLLHuCiIMGT+DW6DoJRtRIS+JieVWBoX0YsWgYInXrVlWUv6gDng5AyVFkUIFwZk7/3mVAgvXO83ArVKA4S747jT60w5bgV4Jy55slDM=", + "hash": "28560680247074990771744165492810964987846406526367865642032954725768850073454" } }, "TokenContract": { @@ -103,8 +103,8 @@ } }, "verificationKey": { - "data": "AAAZwDF1RKCTYA0THXubEEIz2rPDLjxr0qyHA0s1ycjxCNxH03V1odt+L9ccVHLhKFOaGjmlWy2jNTaPu1i8QvEwhvSEdcwDuQvSBXVIwIsFt2KfhAWMak2rupPL+Yx+oApXzgI859KipYtB9cYyKbtTwA/gndNvHRUKiKhsSpe5OxAk0iZRToVR5bGj/oVdJwasvCkd3bE3ef9/LPnF6lIWg8Usxdh4+2Stra2FU6GAY9onRBTTipcK9TxmTiZC3Al6irgSu3HS2k7dNpoJP46I7nadZtBucFC64oUvneu+M8QOqzhJgYv0NYfjdR7WN6owFJBICExQI7I8ig8eY4guYs1OQoQr6677/iwM31wxkl4yJz52FJUzvk6Q19PEHx93lhbGUeParFpdaIqvajTcD47gKzN2DitOGebD+7N6FYjD4byClyJcjrbtpgu/GycYjuN7lL5/3LOkEanA+BsRHD9JH2wY229iHbPaFht6x22QgRR/W8lU94awgYb3hzwwxaZ1vvc9/2WDRQwZmuCnylwUNKbZaNqFQlPLBveqAxYR4bU+ybxoDKBKjOJPxDZvCpBcPonD9KSw8wVppNkRAKWmsS1e5Y5Hd+Hq2JcVLE0bvUQsARXVWZd9nC65G5soweLB40tpfRoixOrHS2WbOwdbh3Jl2o2ka50DM0jobAd8vHRh+D0FJQnCSSoVR5OWw0RbQSrJqjCkORhn6cAoE5IxCodxK/1PDtU+6yyqFPvdNQpF20FU5yFYTAceut00wGyP0gQmiXK0u6Vk99ucyNtK3zFT2lExl7fqZ1TbhiWSrXCjq3UXD1MO8R08TNPldRdzo1FOLk4qRC74MJs0G41u3CG6O2aS6OSy+OQNvFo3bNPQNadNXiL8MK5bWeUhaifJvWJ1Gu/YDyhv/aiomqnJd9OM0c5U2PyqILTlDxEfLyrNbZ7k0/Md1NtQNOLdk63ClVuJOhF6PPxH3NRbMQU4/nnGIkH72Opr8hvuUq5GzQ4SPr0slgQ2HJtP6BI1bYbl07+WH0yF+Msnc3mkus2jyBHN3KmrdEW4wYRq7Qx3sN4vHuibxYvV4qRrhQW3kXLCOMZOpNv7gdSs2fbMHTLqFfgn70Roih7fuFGh3pRM+1l9pN1231Byq66XXvkrjgpuDJIHh2WuHzFkxUTAds76fUsU+2mhx6bh4nZaWC5n5RhhokXst2DgpVG3BvfWTQn7Hsfbt77wTRcdoYPJB967UH48rCqxH+Fcr8HK65t+BiuJwJE0H2FKlVVdypAlNhB01pTR9dPxtePA54ip/mcOuqNPq4FVMLrgmMkcYBZ+WzlsGtnFdtfvGlYsKo4vbqGBv5x6LYEYsxz2i6nUDJQq0a496IeSIrXMFZfp/FTlOtrel3QUH7Cwu/CMQrokVFjoZGXQB5Dqs+0dciFhamuFKNHUUHtnRkHo/cXrBwSFaJCDTFPaPxNDb5amQVP3JW3AI3WlZTTI93siA01iDNdUvWFuYrJOGJ3A+W0Tzu7/qzoKCkAMqApbh97tH3wprl9x1AUrRZJjiOTa1Oxi9xPNs72p3jHUef2BSxVr3g20rGQxbH8zxRvuvsqA0HGuMozQvVXfsde1HU1IoEqKJSmx1ay85BzKLlYxj6HYQ8MKA+VUDUudhxIJXy4beS4Xb0wKuu8NqbNQosHiDkV1ApW5FeF5phxkh75RIj1K9CC/X+gekcbwhomWbMVr2Kd4vKzsXW2Ve6f8k/0hnucvFVMItIVmLR4sTFzu6twAbtMrHgYBfQBe3djzcNVJjpgN67K1zp6VLDq62nqnbMNj+O6R4q5AAbfLNqEK6VNPridK2J5h6ox5MPdqPEIaHDGMWkk/RlxCMvUdbENp5ZxTMwAXFyKWhExaMiAC8vdoYwPbwCE/mGTLbwwHGR6EZti+HNe/dGVHZQrxROubh01pe0pSCPgthD/t4RkJYqNztI0vBHyHuzFfpOJx2I+Pz0XSOV7EhjeOXkgEUT3TsxYZNRpCPIoNs7IS8IqggIRjXErnWtX+onpt37YQQjeKbuVgFKqzCygaSAdAI8Xly3rYg0irkJ5cdItcppJ3IKrA5fIMkbsN9KbDtXG8y+zL41NShxDoGTsFsuJj8HZ004zcvBfl9h+RlJCtKAS+vqkg3gDxcupRDQFOIm3TxenSwUDwO7rDh/2PRsAfCbSTFJYZI/dCOan1ad6DTR6sGMVL++wY6QlVaZJ7M8H2msMWsH5XnGBofluUiHkvEzl3lfHmbClf4ML6MmwHvHt9rJusq44pWyFNOMkZVXwFlhmzamb/P2jZcddoD6wT6s2hLRXC1bcv3kLNJzxgm1eOVAK1qewa5bClvBspu1FbqWDN/LJnDOdtF9ipcUiRjtUCU4BQ0gw=", - "hash": "4358987797077546280184106788898895436156069014510415466528136695410376660075" + "data": "AAAVRdJJF0DehjdPSA0kYGZTkzSfoEaHqDprP5lbtp+BLeGqblAzBabKYB+hRBo7ijFWFnIHV4LwvOlCtrAhNtk/Ae0EY5Tlufvf2snnstKNDXVgcRc/zNAaS5iW43PYqQnEYsaesXs/y5DeeEaFxwdyujsHSK/UaltNLsCc34RKG71O/TGRVVX/eYb8saPPV9W5YjPHLQdhqcHRU6Qq7hMEI1ejTXMokQcurz7jtYU/P56OYekAREejgrEV38U82BbgJigOmh5NhgGTBSAhJ35c9XCsJldUMd5xZiua9cWxGOHm0r7TkcCrV9CEPm5sT7sP7IYQ5dnSdPoi/sy7moUPRitxw7iGvewRVXro6rIemmbxNSzKXWprnl6ewrB2HTppMUEZRp7zYkFIaNDHpvdw4dvjX6K/i527/jwX0JL4BideRc+z3FNhj1VBSHhhvMzwFW6aUwSmWC4UCuwDBokkkBtUE0YYH8kwFnMoWWAlDzHekrxaVmxWRS0lvkr8IDlsR5kyq8SMXFLgKJjoFr6HZWE4tkO/abEgrsK1A3c9F5r/G2yUdMQZu8JMwxUY5qw7D09IPsUQ63c5/CJpea8PAHbUlzRl2KhAhm58JzY0th81wwK0uXhv2e0aXMoEpM0YViAu+c/32zmBe6xl97uBNmNWwlWOLEpHakq46OzONidU3betWNXGJbS4dC4hTNfWM956bK+fwkIlwhM3BC+wOai+M0+y9/y/RSI8qJkSU3MqOF9+nrifKRyNQ3KILqIyR7LjE0/Z/4NzH7eF3uZTBlqfLdf8WhXdwvOPoP1dCx1shF6g4Hh9V4myikRZBtkix1cO5FLUNLNAFw+glg1PB1eA+4ATFuFcfMjxDpDjxqCFCyuQ5TaLuNfYMA7fiO0vB6yqtWgSmCOlD/MQqAhHYRMq4PXk3TUQSle8XBZ67T0+gENjIJleTRgZFG6PgIEwHXcsKIvfFAPklTlnY+5sNVw8yBisVaFgw36DrHWNavWvsZM5HwD0h1Wk0hkavjEIz9nTxQU+nsZsR+70ALZ69HljR0fUjNU7qpVmpYBlRiFxA/BWf8qie2wfhSfy6Q1v5Ee4+3vN/mYuS3uF47LkM1dRTanQ73mLIz80yky+lCNkLWHmZtyWjtMsDFNgupc+yc+FvFNjJM/ea6u3PROtSyU3rAlmchkKvxO4qfrd0iqav/WbabGDMJhbugO4TNu1/i5omH8pbsjGGHQXk1UYPoP1SnMVPZ9RXPoWHJn/kePU9QqGxETHF4T7b2Ov7CcZDLuz147VCknmGiziHzbmYJleu4tzSlFsxHPkp2d9JiDUbO7X66Dh/+84gc5KWpMnEIAF9gITi3cXUglZTjWaASaXcpgHXXGZHZJcrG2VfPNjgTKJ1+CbvyXlvuhvX+0E2oaPB+BoP0i2iTXQHPNhOY/Gg2h6uKvE5fSSiYC7Rws2TGF1aEM54wX3Ti1qA1cAiNG5y8yk1YMGCk3TPqs9MRp0qjgjJbbvFlbgPkkqz5o6c7g8gfhIa4VEJyyI2joqJeIc7vMZFWhquSFHNs0TZKvKLiSAsyNDrpWZb/1PHxziswKvisk296AJi7hmlM1pKx6S4LlbT2OKLXbgq5HUKfe8QhxG4aOsPSSiVGwvnCrIPdSxLq77M27UWXnXHC8mmJmOsGUFj+bdX/u6AgrBhw/w74dDbuNEpC80PbJTuglF/TeDryYsFWCrBnF/WPstgzy3zDDTZ3DXHVYVxOEvErIynlQEY9Cv9QSxRI3dA+hLtob/L78ZeJSU4Al+Qv0QGZTOxQORosVshOP2eFQ1VMKGWOpCVvyi8QE4fa+gOgYT0JRm4rkQBZ5WDlYGkamD3euC92Kd7Z39G89h/AqeFACahkAW1a78SzLW69mZ+CDLfKp/xQsi2TWgJqGh7QNOEtMnn/2owLzLWd071mvUtT0484Eqx6hUqLJMH70p8oUjQIMsh0mvp1BWSU8XC6z+UZIpVm2CERrV8BMLmTLOgTNJlEIJQR7zzpJCDFNNOI+Y2ZtdcuU8XHgcsQhQ3PgCACFAWN3rO+goXoTWdYR/LcqszKzPnMArmPIHWkRM6Mkm13OsHXCVudUbqQjC/pNQZH1VW+RMXnre1vQVb3fnCy5h28Dce3Q2WzjBSZFhe3iADZpo7gWHM/sqe+Mbnbn8A+RRWVNbtjss9376jN73zV4xPH3un3VjTxrzCluqR8MbH8t7mhPBqV5CslmSIbDNruVXtwCf4VS1nssw63PfLzeOSvzhTTsg82rna/+TKl1RIwhD8VFnCDq/Rk8fdy/+K5qP6GcSTbh6J8ERx4jOOukL9TUCpJkhvo/3ED8GOewmWAwzL8avXuf9AFvhwH3ENp5v4IIGBljuDJ77vckGmTI=", + "hash": "13796172868423455932596117465273580383420853883879480382066094121613342871544" } }, "Dex": { @@ -132,8 +132,8 @@ } }, "verificationKey": { - "data": "AAAn6e1t6dmR6nhB5BpgRjDAquZiTshYDyjs17DkRTUQMOJPMAJWE8MBdybFeHIINuCU8gFYI6WVL7RfnhhENHQjoaXg+jOKxlRuYp86axcshCaI2Z6lp7avzuu5nOu7yyfN62zx0IfPZRfKSQHEKU21WrAcBApWvY2KhF3gusz3PhR7Dfq4ckf9H+xv+a4ubCIg8MkgDV7Wcb6IR1EFOhsZnXzL9FbAyWknFdt4IX7xDX9ewK/8JFem+/KaG/CeghvHgHbnLNUbaWqiz6C4Y61xTkOYrBwny/tlrIKkTEWqHQogyRdkffmhgQmzIHcKcciqeZgyxPzGc8V1lcn4NeIZOvzO8X/XqZafgygMG51oQ2S/5U9D9GFJuxLfblX0cj9Ayo6jSwMgw8t67+T9LK3RPP1cu5SVJHYNPUBDD8FOAyQLfdRQfnQ/JYB85QBkjL0deUGDz5VmIrhXYltYBhMvcbEd2gpnsRhvSZ5oxnD5haF9Mt/2FNpBuLqKkj9cggkyp3/m8AFRQ9DPLArTuItYf9oqhMKh7QY/gvmcwjiVGS+k7UHapV8cThpM3qZ59euRVOw/enq6L0W0imM05T8FABJP1EQWTv5uUamSDC+LESUrSVH6CQX2LslQ8DkyKcg5wK0lZf8AWYaYUcQwZdXEIFk1zWOgYplQOPNXfSsEEgWRhNDIQxDe+Cfi36MC1NrDfiLOpb7iWDNi8cw/BrplC4PClet1Y3bjqIewbk96d6po5QquPn02R11oVPEZDBIJGUfWhNRYxk3QOB1ale7iewzgE7wO3EjdmFOd5PvzYh1pSt4Wv+x/X4JZsc20x1YZE1xMPa9kpqw4+Wb1cuAtDfqPwGajHhIlzi8Y9FOFlaT9Hz0FKkUSpYj7LW0xJuQD0vLk0QDAqAXJQCIw+36tmPzdjET6WyjgFjnq411fJDAZHrz7TPYwxD59J8x369fkoxW/GWapHW47ruTFeBm0Pdy4PjEHRQASoMHcr1BYKdC2/+Ed7goH0RO/fq0eL/EwSy8dNapzx5/n3b5gOtTJLEk3sdxrm+o7Jw7XklWhbB4dFHoJGcWiEtiSH3w41dwAtgN+nzyjoF7qK8ISCbYQPfq0qhdiStX+dmOYSY4Q0gD4O8FlHxm0SX30ZVrUUYkYhPdjYKS1EiLatN3v4LswE9Y1QIgaHUBO6B2hSqE2tjW/t4MIGAFZ5e0tF6AQ1xQ8d1fgpgURg2QoL08+EZxsJBxC0A3sGHVLMGZllmUWav6M0zxxyZiHjq9zdlJVDOMo/6YzlrVuRkbU8T+V9O0EEUsZ00Re11JHsGraKHSp4jQxVylFOnKwq28ieBF16zabCo/rtQ5Uo6xxMkPDVLJlJs9xTpv/T8Qm4rFW6fS2hq4uk25PZF3qk2DE11yPFPcEwJv0bVAsmjclkVNyN9CTnfuB8/n4Q+0A9zC78fpGrA3QL3fExFGsfQS2sFp30QX8VYOnp569qGGbTuzAG386OlvhlI38Of2CtVkDlYGiIJlDblAE2P6pwOuAjv1doKsxS6VmgoWQ/Y3BaRCFSkm7OCU07KwSwYgRrm81zsqsECanXo7qHIAmmEK8NmoSPcLWmEGOfgTa9AFoEXNyxe8EC9AZFTxyMsc0oSuyM1vQL15XwpJw7xtDHjhs2ICcORAOC996oR92WmNhq1dxG+5AUdEJI/riuI5dSYPap+L60SquwA81Ii7uZEP558TiRG7l6K9J0B9/wWoDVLp2Dzg6OVTsOzShFIFjB/JDaEWHPAadur8h8RLulqHG34WLVuQc3PS7yzDC98wJeEaZNOPdW8nguOAfFhdzcrMLf/VHSyeAb0JrbVS2ahgJXZi1o1xebypTCZMdEu5ViaexTkE2EADMEiwMHAbJYEEkurltgE4txTUGXQlEKGrTjd+XIcytAugvBNXcLIhsoS0RvY8Q6OWtu3sooqbVvA2INhXHP+8TwWxUqoCIZEQ2rM9JXVVqiNkDNxlWk69yC2v8wdSHmCdYiWzN7wn2yvy67/g4OhnccSf6HgiDUz3UEGNz4ON2OtH1lzmdH1llZpbO8Kt/mRLP5tstYzZecd9cLbN525geWWzo00yPr0+QCRkdErW3bsZfzr3xPlTHT8OoelKIXRVFRFNeFqzBlumbnJNY4cqdzRmPokn/SdLasdCoCrOiGhCXSIix3F5zlPvX4l1K6jf1VFaTF9eR/PrtZf/kdDQR4LFWb6Ny+4eatmHXAdBYdBZG6cAGmw3+8NWQOFqWqxyI+UYYdciQI1Q2dzF50JPTcfVBORG6gsge8EPHMOi4IygI3S2pLHVtUuyt3MhjBUqYm6EkeX39Q+qA1702y54+tbh/e5pC4FVpxg8Z85HaxTJJPDbdvVfSLpWI67hSHyA=", - "hash": "15050479156601146522404126538900314464732582521050724089237496619683001122121" + "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAOLg+O5JsHz6EFkkWxwdCSmy1K1LFaS6A78wbTtc9uIslLAntKxTApVE2lxPzH+TwHBFMkSweXxdP3dGxtecxxpbbLKvz9Clh3WpX0ia/8PSErjEfdpClkDrgo8DG2MpEgFaBcgfyFNTEhXLnxCiGlwjJ+DdBAfnonMPIkkY6p0SJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AM84dcwR1Ur7O0YSVR5TXXJhMigCPYsF0/fmLijOWNAga8rtMJvF0oZ02aoQv4KpGu9yq72CsoXSpWqu/6C+GSP52zL9QV0VkohE1njGsSrC/EMtWuNxk6avge+WIxnbAbrFVGoWKdAN3uuZBKQW6ehhi1watI+S5lkpbpTnrK3R/59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", + "hash": "6361961148584909856756402479432671413765163664396823312454174383287651676472" } }, "Group Primitive": { From 00baab94486e474b4da15803b5263d1d2895bed1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 09:39:16 +0200 Subject: [PATCH 0159/1215] lift logic from bindings test into test lib --- src/lib/testing/equivalent.ts | 71 ++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index c50c662a0a..0a60f7986e 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -5,7 +5,70 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; -export { createEquivalenceTesters, throwError, handleErrors }; +export { + equivalent, + createEquivalenceTesters, + throwError, + handleErrors, + deepEqual as defaultAssertEqual, + id, +}; +export { Spec, ToSpec, FromSpec, SpecFromFunctions }; + +// a `Spec` tells us how to compare two functions + +type FromSpec = { + // `rng` creates random inputs to the first function + rng: Random; + + // `there` converts to inputs to the second function + there: (x: In1) => In2; +}; + +type ToSpec = { + // `back` converts outputs of the second function back to match the first function + back: (x: Out2) => Out1; + + // `assertEqual` to compare outputs against each other; defaults to `deepEqual` + assertEqual?: (x: Out1, y: Out1, message: string) => void; +}; + +type Spec = FromSpec & ToSpec; + +type FuncSpec, Out1, In2 extends Tuple, Out2> = { + from: { + [k in keyof In1]: k extends keyof In2 ? FromSpec : never; + }; + to: ToSpec; +}; + +type SpecFromFunctions< + F1 extends AnyFunction, + F2 extends AnyFunction +> = FuncSpec, ReturnType, Parameters, ReturnType>; + +function equivalent, Out1, In2 extends Tuple, Out2>( + { from, to }: FuncSpec, + f1: (...args: In1) => Out1, + f2: (...args: In2) => Out2, + label?: string +) { + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + test(...(generators as any[]), (...args) => { + args.pop(); + let inputs = args as any as In1; + handleErrors( + () => f1(...inputs), + () => + to.back(f2(...(inputs.map((x, i) => from[i].there(x)) as any as In2))), + (x, y) => assertEqual(x, y, label ?? 'same results'), + label + ); + }); +} + +let id = (x: T) => x; function createEquivalenceTesters( Field: Provable, @@ -165,3 +228,9 @@ function handleErrors( function throwError(message?: string): any { throw Error(message); } + +// helper types + +type AnyFunction = (...args: any) => any; + +type Tuple = [] | [T, ...T[]]; From 0876d8f71e5d4172749a2807e50df5a51eadc6eb Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 11:35:34 +0200 Subject: [PATCH 0160/1215] abstract provable equivalence tests using function specs --- src/lib/field.unit-test.ts | 2 +- src/lib/testing/equivalent.ts | 206 +++++++++++++++++----------------- 2 files changed, 106 insertions(+), 102 deletions(-) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index f28f688bb3..67f007ab79 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -63,7 +63,7 @@ let SmallField = Random.reject( ); let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = - createEquivalenceTesters(Field, Field); + createEquivalenceTesters(); // arithmetic, both in- and outside provable code equivalent2((x, y) => x.add(y), Fp.add); diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 0a60f7986e..bf4aa22882 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -4,6 +4,7 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; +import { Field } from '../core.js'; export { equivalent, @@ -51,7 +52,7 @@ function equivalent, Out1, In2 extends Tuple, Out2>( { from, to }: FuncSpec, f1: (...args: In1) => Out1, f2: (...args: In2) => Out2, - label?: string + label = 'expect equal results' ) { let generators = from.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; @@ -62,134 +63,122 @@ function equivalent, Out1, In2 extends Tuple, Out2>( () => f1(...inputs), () => to.back(f2(...(inputs.map((x, i) => from[i].there(x)) as any as In2))), - (x, y) => assertEqual(x, y, label ?? 'same results'), + (x, y) => assertEqual(x, y, label), label ); }); } -let id = (x: T) => x; +function id(x: T) { + return x; +} -function createEquivalenceTesters( - Field: Provable, - newField: (x: bigint) => Field -) { - function equivalent1( - op1: (x: Field) => Field, - op2: (x: bigint) => bigint, - rng: Random = Random.field +// equivalence in provable code + +type ProvableSpec = Spec & { provable: Provable }; +type MaybeProvableFromSpec = FromSpec & { + provable?: Provable; +}; + +function equivalentProvable< + In extends Tuple>, + Out extends ToSpec +>({ from, to }: { from: In; to: Out }) { + return function run( + f1: (...args: Params1) => Result1, + f2: (...args: Params2) => Result2, + label = 'expect equal results' ) { - test(rng, (x0, assert) => { - let x = newField(x0); + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + test(...(generators as any[]), (...args) => { + args.pop(); + let inputs = args as any as Params1; + let inputs2 = inputs.map((x, i) => + from[i].there(x) + ) as any as Params2; + // outside provable code handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => assert(a.toBigInt() === b, 'equal results') + () => f1(...inputs), + () => f2(...inputs2), + (x, y) => assertEqual(x, to.back(y), label), + label ); + // inside provable code Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); + let inputWitnesses = inputs2.map((x, i) => { + let provable = from[i].provable; + return provable !== undefined + ? Provable.witness(provable, () => x) + : x; + }) as any as Params2; handleErrors( - () => op1(x), - () => op2(x0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + () => f1(...inputs), + () => f2(...inputWitnesses), + (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)) ); }); }); + }; +} + +// some useful specs + +let unit: ToSpec = { back: id, assertEqual() {} }; + +let field: ProvableSpec = { + rng: Random.field, + there: (x) => new Field(x), + back: (x) => x.toBigInt(), + provable: Field, +}; + +let fieldBigint: Spec = { + rng: Random.field, + there: id, + back: id, +}; + +// old equivalence testers + +function createEquivalenceTesters() { + function equivalent1( + f1: (x: Field) => Field, + f2: (x: bigint) => bigint, + rng: Random = Random.field + ) { + let field_ = { ...field, rng }; + equivalentProvable({ from: [field_], to: field_ })(f2, f1); } function equivalent2( - op1: (x: Field, y: Field | bigint) => Field, - op2: (x: bigint, y: bigint) => bigint, + f1: (x: Field, y: Field | bigint) => Field, + f2: (x: bigint, y: bigint) => bigint, rng: Random = Random.field ) { - test(rng, rng, (x0, y0, assert) => { - let x = newField(x0); - let y = newField(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => assert(a.toBigInt() === b, 'equal results') - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0), - (a, b) => - Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) - ); - }); - }); + let field_ = { ...field, rng }; + let fieldBigint_ = { ...fieldBigint, rng }; + equivalentProvable({ from: [field_, field_], to: field_ })(f2, f1); + equivalentProvable({ from: [field_, fieldBigint_], to: field_ })(f2, f1); } function equivalentVoid1( - op1: (x: Field) => void, - op2: (x: bigint) => void, + f1: (x: Field) => void, + f2: (x: bigint) => void, rng: Random = Random.field ) { - test(rng, (x0) => { - let x = newField(x0); - // outside provable code - handleErrors( - () => op1(x), - () => op2(x0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - handleErrors( - () => op1(x), - () => op2(x0) - ); - }); - }); + let field_ = { ...field, rng }; + equivalentProvable({ from: [field_], to: unit })(f2, f1); } function equivalentVoid2( - op1: (x: Field, y: Field | bigint) => void, - op2: (x: bigint, y: bigint) => void, + f1: (x: Field, y: Field | bigint) => void, + f2: (x: bigint, y: bigint) => void, rng: Random = Random.field ) { - test(rng, rng, (x0, y0) => { - let x = newField(x0); - let y = newField(y0); - // outside provable code - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - // inside provable code - Provable.runAndCheck(() => { - x = Provable.witness(Field, () => x); - y = Provable.witness(Field, () => y); - handleErrors( - () => op1(x, y), - () => op2(x0, y0) - ); - handleErrors( - () => op1(x, y0), - () => op2(x0, y0) - ); - }); - }); + let field_ = { ...field, rng }; + let fieldBigint_ = { ...fieldBigint, rng }; + equivalentProvable({ from: [field_, field_], to: unit })(f2, f1); + equivalentProvable({ from: [field_, fieldBigint_], to: unit })(f2, f1); } return { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 }; @@ -234,3 +223,18 @@ function throwError(message?: string): any { type AnyFunction = (...args: any) => any; type Tuple = [] | [T, ...T[]]; + +// infer input types from specs + +type Params1>> = { + [k in keyof Ins]: Ins[k] extends FromSpec ? In : never; +}; +type Params2>> = { + [k in keyof Ins]: Ins[k] extends FromSpec ? In : never; +}; +type Result1> = Out extends ToSpec + ? Out1 + : never; +type Result2> = Out extends ToSpec + ? Out2 + : never; From 990730de3ea2c198d5c5c0e62b28d6703ad2ec09 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:01:12 +0200 Subject: [PATCH 0161/1215] add gate definitions --- src/snarky.d.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 7a0f16f129..014e888402 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -298,6 +298,26 @@ declare const Snarky: { ], compact: FieldConst ): void; + + xor( + in1: FieldVar, + in2: FieldVar, + out: FieldVar, + in1_0: FieldVar, + in1_1: FieldVar, + in1_2: FieldVar, + in1_3: FieldVar, + in2_0: FieldVar, + in2_1: FieldVar, + in2_2: FieldVar, + in2_3: FieldVar, + out_0: FieldVar, + out_1: FieldVar, + out_2: FieldVar, + out_3: FieldVar + ): void; + + zeroCheck(in1: FieldVar, in2: FieldVar, out: FieldVar): void; }; bool: { From b108d96a23b128b399565d3968a08e6ad07f37ab Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:01:32 +0200 Subject: [PATCH 0162/1215] add gates --- src/lib/gates.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 503c4d2c6a..a585dbf2ef 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64 }; +export { rangeCheck64, xor, zeroCheck }; /** * Asserts that x is at most 64 bits @@ -42,6 +42,49 @@ function rangeCheck64(x: Field) { ); } +/** + * + */ +function xor( + input1: Field, + input2: Field, + outputXor: Field, + in1_0: Field, + in1_1: Field, + in1_2: Field, + in1_3: Field, + in2_0: Field, + in2_1: Field, + in2_2: Field, + in2_3: Field, + out_0: Field, + out_1: Field, + out_2: Field, + out_3: Field +) { + Snarky.gates.xor( + input1.value, + input2.value, + outputXor.value, + in1_0.value, + in1_1.value, + in1_2.value, + in1_3.value, + in2_0.value, + in2_1.value, + in2_2.value, + in2_3.value, + out_0.value, + out_1.value, + out_2.value, + out_3.value + ); +} + +function zeroCheck(in1: Field, in2: Field, out: Field) { + Snarky.gates.zeroCheck(in1.value, in2.value, out.value); +} + function getBits(x: bigint, start: number, length: number) { return FieldConst.fromBigint( (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n) From 958438286d7bbb849d696d7a1cc72ddb370d4d88 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:01:43 +0200 Subject: [PATCH 0163/1215] first draft of bitwise XOR gadget --- src/lib/gadgets/bitwise.ts | 212 +++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/lib/gadgets/bitwise.ts diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts new file mode 100644 index 0000000000..0b672129f8 --- /dev/null +++ b/src/lib/gadgets/bitwise.ts @@ -0,0 +1,212 @@ +import { Snarky } from 'src/snarky.js'; +import { Field, FieldConst, FieldVar } from '../field.js'; +import { Provable } from '../provable.js'; +import * as Gates from '../gates.js'; + +/** + * Boolean Xor of length bits + * input1 and input2 are the inputs to the Xor gate + * length is the number of bits to Xor + * len_xor is the number of bits of the lookup table (default is 4) + */ +function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { + // check that both input lengths are positive + assert( + length > 0 && len_xor < 0, + `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + ); + + // check that length does not exceed maximum field size in bits + assert( + length <= Field.sizeInBits(), + 'Length exceeds maximum field size in bits.' + ); + + if (input1.isConstant() && input2.isConstant()) { + throw Error('TODO constant case'); + } + + // calculates next variable for the chain as provr + function nextVariable(): Field { + return new Field(5); + } + + // builds xor chain recurisvely + function xorRec( + input1: Field, + input2: Field, + outputXor: FieldVar, + padLength: number, + len_xor: number + ) { + // if inputs are zero and length is zero, add the zero check + + if (length == 0) { + Gates.zeroCheck(input1, input2, new Field(outputXor)); + + let zero = new Field(0); + zero.assertEquals(input1); + zero.assertEquals(input2); + zero.assertEquals(new Field(outputXor)); + } else { + function ofBits(f: Field, start: number, stop: number) { + if (stop !== -1 && stop <= start) + throw Error('Stop offste must be greater than start offset'); + + return Provable.witness(Field, () => + fieldBitsToFieldLE(f, start, stop) + ); + } + + // nibble offsets + let first = len_xor; + let second = first + len_xor; + let third = second + len_xor; + let fourth = third + len_xor; + + let in1_0 = ofBits(input1, 0, first); + let in1_1 = ofBits(input1, first, second); + let in1_2 = ofBits(input1, second, third); + let in1_3 = ofBits(input1, third, fourth); + let in2_0 = ofBits(input2, 0, first); + let in2_1 = ofBits(input2, first, second); + let in2_2 = ofBits(input2, second, third); + let in2_3 = ofBits(input2, third, fourth); + let out_0 = ofBits(new Field(outputXor), 0, first); + let out_1 = ofBits(new Field(outputXor), first, second); + let out_2 = ofBits(new Field(outputXor), second, third); + let out_3 = ofBits(new Field(outputXor), third, fourth); + + Gates.xor( + input1, + input2, + new Field(outputXor), + in1_0, + in1_1, + in1_2, + in1_3, + in2_0, + in2_1, + in2_2, + in2_3, + out_0, + out_1, + out_2, + out_3 + ); + + let next_in1 = asProverNextVar( + input1, + in1_0, + in1_1, + in1_2, + in1_3, + len_xor + ); + } + } + + // create two input arrays of length 255 set to false + let input1Array = new Array(length).fill(false); + let input2Array = new Array(length).fill(false); + + // sanity check as prover about lengths of inputs + Provable.asProver(() => { + // check real lengths are at most the desired length + fitsInBits(input1, length); + fitsInBits(input2, length); + + // converts input field elements to list of bits of length 255 + let input1Bits = input1.toBits(); + let input2Bits = input2.toBits(); + + // iterate over 255 positions to update value of arrays + for (let i = 0; i < Field.sizeInBits() - 1; i++) { + input1Array[i] = input1Bits[i]; + input2Array[i] = input2Bits[i]; + } + }); + + let outputXor = Snarky.existsVar(() => { + // check real lengths are at most the desired length + fitsInBits(input1, length); + fitsInBits(input2, length); + + // converts input field elements to list of bits of length 255 + let input1Bits = input1.toBits(); + let input2Bits = input2.toBits(); + + let outputBits = input1Bits.map((bit, i) => { + let b1 = bit; + let b2 = input2Bits[i]; + return b1.equals(b2).not(); + }); + + // convert bits to Field + return FieldConst.fromBigint(Field.fromBits(outputBits).toBigInt()); + }); + + // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table + let padLength = len_xor; + if (length % (4 * len_xor) !== 0) { + padLength = length + 4 * len_xor - (length % (4 * len_xor)); + } + + // recurisvely build xor gadget + + xorRec(input1, input2, outputXor, padLength, len_xor); + + // convert back to field + return new Field(outputXor); +} + +function assert(stmt: boolean, message?: string) { + if (!stmt) { + throw Error(message ?? 'Assertion failed'); + } +} + +function fitsInBits(word: Field, length: number) { + Provable.asProver(() => { + assert(word.toBigInt() < 2 ** length, 'Word does not fit in bits'); + }); +} + +function fieldBitsToFieldLE(f: Field, start: number, stop: number) { + if (stop !== -1 && stop <= start) + throw Error('stop offset must be greater than start offset'); + + let bits = f.toBits(); + + if (stop > bits.length) throw Error('stop must be less than bit-length'); + + if (stop === -1) stop = bits.length; + + return Field.fromBits(bits.slice(start, stop)); +} + +function asProverNextVar( + current: Field, + var0: Field, + var1: Field, + var2: Field, + var3: Field, + len_xor: number +) { + let twoPowLen = new Field(2 ** len_xor); + + let twoPow2Len = twoPowLen.mul(twoPowLen); + let twoPow3Len = twoPow2Len.mul(twoPowLen); + let twoPow4Len = twoPow3Len.mul(twoPowLen); + + let nextVar = Provable.witness(Field, () => { + return current + .sub(var0) + .sub(var1.mul(twoPowLen)) + .sub(var2.mul(twoPow2Len)) + .sub(var3.mul(twoPow3Len)) + .div(twoPow4Len); + }); + + return nextVar; +} From 09e3f37ba5aa85d37d1124e934f54d7f63b9e2ff Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:01:50 +0200 Subject: [PATCH 0164/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 91e701cd02..cd5ea42eab 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 91e701cd027d5a1213e750e5e9b80da80c9d211e +Subproject commit cd5ea42eab3f99bcce821b35b57ce93f81b14416 From ca54b508eab4faffdd8e2ccc656897756636f31b Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:05:30 +0200 Subject: [PATCH 0165/1215] WIP bitwise XOR draft --- src/lib/gadgets/bitwise.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 0b672129f8..5090fefcd2 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -41,7 +41,7 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { ) { // if inputs are zero and length is zero, add the zero check - if (length == 0) { + if (padLength == 0) { Gates.zeroCheck(input1, input2, new Field(outputXor)); let zero = new Field(0); @@ -103,6 +103,27 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { in1_3, len_xor ); + + let next_in2 = asProverNextVar( + input2, + in2_0, + in2_1, + in2_2, + in2_3, + len_xor + ); + + let next_out = asProverNextVar( + new Field(outputXor), + out_0, + out_1, + out_2, + out_3, + len_xor + ); + + let next_length = padLength - 4 * len_xor; + xorRec(next_in1, next_in2, next_out.value, next_length, len_xor); } } From b69ac01826c02c59f44af394d229572f18c3d587 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 12:15:31 +0200 Subject: [PATCH 0166/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index cd5ea42eab..b040bde6d8 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit cd5ea42eab3f99bcce821b35b57ce93f81b14416 +Subproject commit b040bde6d8afcb2514aadcf30e80001b4f9b6222 From a2eca3d0ba7fccd1b516b5122cabce9a10bf0351 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 15:16:31 +0200 Subject: [PATCH 0167/1215] support union types in function parameters nicely --- src/lib/testing/equivalent.ts | 99 ++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index bf4aa22882..4557cb47c6 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -8,12 +8,15 @@ import { Field } from '../core.js'; export { equivalent, + equivalentProvable, + oneOf, createEquivalenceTesters, throwError, handleErrors, deepEqual as defaultAssertEqual, id, }; +export { field, fieldBigint }; export { Spec, ToSpec, FromSpec, SpecFromFunctions }; // a `Spec` tells us how to compare two functions @@ -24,6 +27,11 @@ type FromSpec = { // `there` converts to inputs to the second function there: (x: In1) => In2; + + // `provable` tells us how to create witnesses, to test provable code + // note: we only allow the second function to be provable; + // the second because it's more natural to have non-provable types as random generator output + provable?: Provable; }; type ToSpec = { @@ -36,6 +44,8 @@ type ToSpec = { type Spec = FromSpec & ToSpec; +type ProvableSpec = Spec & { provable: Provable }; + type FuncSpec, Out1, In2 extends Tuple, Out2> = { from: { [k in keyof In1]: k extends keyof In2 ? FromSpec : never; @@ -73,30 +83,60 @@ function id(x: T) { return x; } -// equivalence in provable code +// unions of specs, to cleanly model functions parameters that are unions of types -type ProvableSpec = Spec & { provable: Provable }; -type MaybeProvableFromSpec = FromSpec & { - provable?: Provable; +type FromSpecUnion = { + _isUnion: true; + specs: Tuple>; + rng: Random<[number, T1]>; }; +type OrUnion = FromSpec | FromSpecUnion; + +type Union = T[keyof T & number]; + +function oneOf>>( + ...specs: In +): FromSpecUnion>, Union>> { + // the randomly generated value from a union keeps track of which spec it came from + let rng = Random.oneOf( + ...specs.map((spec, i) => + Random.map(spec.rng, (x) => [i, x] as [number, any]) + ) + ); + return { _isUnion: true, specs, rng }; +} + +function toUnion(spec: OrUnion): FromSpecUnion { + let specAny = spec as any; + return specAny._isUnion ? specAny : oneOf(specAny); +} + +// equivalence in provable code + function equivalentProvable< - In extends Tuple>, + In extends Tuple>, Out extends ToSpec ->({ from, to }: { from: In; to: Out }) { +>({ from: fromRaw, to }: { from: In; to: Out }) { + let fromUnions = fromRaw.map(toUnion); return function run( f1: (...args: Params1) => Result1, f2: (...args: Params2) => Result2, label = 'expect equal results' ) { - let generators = from.map((spec) => spec.rng); + let generators = fromUnions.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; - test(...(generators as any[]), (...args) => { + test(...generators, (...args) => { args.pop(); - let inputs = args as any as Params1; - let inputs2 = inputs.map((x, i) => - from[i].there(x) - ) as any as Params2; + + // figure out which spec to use for each argument + let from = (args as [number, unknown][]).map( + ([j], i) => fromUnions[i].specs[j] + ); + let inputs = (args as [number, unknown][]).map( + ([, x]) => x + ) as Params1; + let inputs2 = inputs.map((x, i) => from[i].there(x)) as Params2; // outside provable code handleErrors( @@ -113,7 +153,7 @@ function equivalentProvable< return provable !== undefined ? Provable.witness(provable, () => x) : x; - }) as any as Params2; + }) as Params2; handleErrors( () => f1(...inputs), () => f2(...inputWitnesses), @@ -144,13 +184,8 @@ let fieldBigint: Spec = { // old equivalence testers function createEquivalenceTesters() { - function equivalent1( - f1: (x: Field) => Field, - f2: (x: bigint) => bigint, - rng: Random = Random.field - ) { - let field_ = { ...field, rng }; - equivalentProvable({ from: [field_], to: field_ })(f2, f1); + function equivalent1(f1: (x: Field) => Field, f2: (x: bigint) => bigint) { + equivalentProvable({ from: [field], to: field })(f2, f1); } function equivalent2( f1: (x: Field, y: Field | bigint) => Field, @@ -226,12 +261,28 @@ type Tuple = [] | [T, ...T[]]; // infer input types from specs -type Params1>> = { - [k in keyof Ins]: Ins[k] extends FromSpec ? In : never; +type Param1> = In extends { + there: (x: infer In) => any; +} + ? In + : In extends FromSpecUnion + ? T1 + : never; +type Param2> = In extends { + there: (x: any) => infer In; +} + ? In + : In extends FromSpecUnion + ? T2 + : never; + +type Params1>> = { + [k in keyof Ins]: Param1; }; -type Params2>> = { - [k in keyof Ins]: Ins[k] extends FromSpec ? In : never; +type Params2>> = { + [k in keyof Ins]: Param2; }; + type Result1> = Out extends ToSpec ? Out1 : never; From af2444d0f233b7de641fe371d8d5fd58d2adf95b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 15:44:33 +0200 Subject: [PATCH 0168/1215] refactor field unit test to new style --- src/lib/field.unit-test.ts | 101 +++++++++++++++++++--------------- src/lib/testing/equivalent.ts | 56 +++++-------------- 2 files changed, 69 insertions(+), 88 deletions(-) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 67f007ab79..8f7d8843f5 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -7,7 +7,16 @@ import { Provable } from './provable.js'; import { Binable } from '../bindings/lib/binable.js'; import { ProvableExtended } from './circuit_value.js'; import { FieldType } from './field.js'; -import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; +import { + equivalentProvable as equivalent, + oneOf, + field, + bigintField, + throwError, + unit, + bool, + Spec, +} from './testing/equivalent.js'; // types Field satisfies Provable; @@ -56,73 +65,75 @@ test(Random.field, Random.int(-5, 5), (x, k) => { deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); }); +// 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 { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } = - createEquivalenceTesters(); +let smallField: Spec = { ...field, rng: SmallField }; +let smallBigint: Spec = { ...bigintField, rng: SmallField }; +let smallFieldOrBigint = oneOf(smallField, smallBigint); // arithmetic, both in- and outside provable code -equivalent2((x, y) => x.add(y), Fp.add); -equivalent1((x) => x.neg(), Fp.negate); -equivalent2((x, y) => x.sub(y), Fp.sub); -equivalent2((x, y) => x.mul(y), Fp.mul); +let equivalent1 = equivalent({ from: [field], to: field }); +let equivalent2 = equivalent({ from: [field, fieldOrBigint], to: field }); + +equivalent2(Fp.add, (x, y) => x.add(y)); +equivalent1(Fp.negate, (x) => x.neg()); +equivalent2(Fp.sub, (x, y) => x.sub(y)); +equivalent2(Fp.mul, (x, y) => x.mul(y)); equivalent1( - (x) => x.inv(), - (x) => Fp.inverse(x) ?? throwError('division by 0') + (x) => Fp.inverse(x) ?? throwError('division by 0'), + (x) => x.inv() ); equivalent2( - (x, y) => x.div(y), - (x, y) => Fp.div(x, y) ?? throwError('division by 0') + (x, y) => Fp.div(x, y) ?? throwError('division by 0'), + (x, y) => x.div(y) ); -equivalent1((x) => x.square(), Fp.square); +equivalent1(Fp.square, (x) => x.square()); equivalent1( - (x) => x.sqrt(), - (x) => Fp.sqrt(x) ?? throwError('no sqrt') + (x) => Fp.sqrt(x) ?? throwError('no sqrt'), + (x) => x.sqrt() ); -equivalent2( - (x, y) => x.equals(y).toField(), - (x, y) => BigInt(x === y) +equivalent({ from: [field, fieldOrBigint], to: bool })( + (x, y) => x === y, + (x, y) => x.equals(y) ); -equivalent2( - (x, y) => x.lessThan(y).toField(), - (x, y) => BigInt(x < y), - SmallField + +equivalent({ from: [smallField, smallFieldOrBigint], to: bool })( + (x, y) => x < y, + (x, y) => x.lessThan(y) ); -equivalent2( - (x, y) => x.lessThanOrEqual(y).toField(), - (x, y) => BigInt(x <= y), - SmallField +equivalent({ from: [smallField, smallFieldOrBigint], to: bool })( + (x, y) => x <= y, + (x, y) => x.lessThanOrEqual(y) ); -equivalentVoid2( - (x, y) => x.assertEquals(y), - (x, y) => x === y || throwError('not equal') +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x === y || throwError('not equal'), + (x, y) => x.assertEquals(y) ); -equivalentVoid2( - (x, y) => x.assertNotEquals(y), - (x, y) => x !== y || throwError('equal') +equivalent({ from: [field, fieldOrBigint], to: unit })( + (x, y) => x !== y || throwError('equal'), + (x, y) => x.assertNotEquals(y) ); -equivalentVoid2( - (x, y) => x.assertLessThan(y), +equivalent({ from: [smallField, smallFieldOrBigint], to: unit })( (x, y) => x < y || throwError('not less than'), - SmallField + (x, y) => x.assertLessThan(y) ); -equivalentVoid2( - (x, y) => x.assertLessThanOrEqual(y), +equivalent({ from: [smallField, smallFieldOrBigint], to: unit })( (x, y) => x <= y || throwError('not less than or equal'), - SmallField + (x, y) => x.assertLessThanOrEqual(y) ); -equivalentVoid1( - (x) => x.assertBool(), - (x) => x === 0n || x === 1n || throwError('not boolean') +equivalent({ from: [field], to: unit })( + (x) => x === 0n || x === 1n || throwError('not boolean'), + (x) => x.assertBool() ); -equivalent1( - (x) => x.isEven().toField(), - (x) => BigInt((x & 1n) === 0n), - SmallField +equivalent({ from: [smallField], to: bool })( + (x) => (x & 1n) === 0n, + (x) => x.isEven() ); // non-constant field vars diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 4557cb47c6..014437aa9a 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -4,20 +4,19 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; -import { Field } from '../core.js'; +import { Bool, Field } from '../core.js'; export { equivalent, equivalentProvable, oneOf, - createEquivalenceTesters, throwError, handleErrors, deepEqual as defaultAssertEqual, id, }; -export { field, fieldBigint }; -export { Spec, ToSpec, FromSpec, SpecFromFunctions }; +export { field, bigintField, bool, unit }; +export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; // a `Spec` tells us how to compare two functions @@ -83,7 +82,7 @@ function id(x: T) { return x; } -// unions of specs, to cleanly model functions parameters that are unions of types +// unions of specs, to cleanly model function parameters that are unions of types type FromSpecUnion = { _isUnion: true; @@ -170,54 +169,25 @@ let unit: ToSpec = { back: id, assertEqual() {} }; let field: ProvableSpec = { rng: Random.field, - there: (x) => new Field(x), + there: Field, back: (x) => x.toBigInt(), provable: Field, }; -let fieldBigint: Spec = { +let bigintField: Spec = { rng: Random.field, there: id, back: id, }; -// old equivalence testers - -function createEquivalenceTesters() { - function equivalent1(f1: (x: Field) => Field, f2: (x: bigint) => bigint) { - equivalentProvable({ from: [field], to: field })(f2, f1); - } - function equivalent2( - f1: (x: Field, y: Field | bigint) => Field, - f2: (x: bigint, y: bigint) => bigint, - rng: Random = Random.field - ) { - let field_ = { ...field, rng }; - let fieldBigint_ = { ...fieldBigint, rng }; - equivalentProvable({ from: [field_, field_], to: field_ })(f2, f1); - equivalentProvable({ from: [field_, fieldBigint_], to: field_ })(f2, f1); - } - function equivalentVoid1( - f1: (x: Field) => void, - f2: (x: bigint) => void, - rng: Random = Random.field - ) { - let field_ = { ...field, rng }; - equivalentProvable({ from: [field_], to: unit })(f2, f1); - } - function equivalentVoid2( - f1: (x: Field, y: Field | bigint) => void, - f2: (x: bigint, y: bigint) => void, - rng: Random = Random.field - ) { - let field_ = { ...field, rng }; - let fieldBigint_ = { ...fieldBigint, rng }; - equivalentProvable({ from: [field_, field_], to: unit })(f2, f1); - equivalentProvable({ from: [field_, fieldBigint_], to: unit })(f2, f1); - } +let bool: Spec = { + rng: Random.boolean, + there: Bool, + back: (x) => x.toBoolean(), + provable: Bool, +}; - return { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 }; -} +// helper to ensure two functions throw equivalent errors function handleErrors( op1: () => T, From 3bf09f966f6cb2e6310d5f8d86584256d5c730df Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 15:57:20 +0200 Subject: [PATCH 0169/1215] make non provable equivalence tester more similar --- src/lib/testing/equivalent.ts | 54 ++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 014437aa9a..77cd08634f 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -57,27 +57,6 @@ type SpecFromFunctions< F2 extends AnyFunction > = FuncSpec, ReturnType, Parameters, ReturnType>; -function equivalent, Out1, In2 extends Tuple, Out2>( - { from, to }: FuncSpec, - f1: (...args: In1) => Out1, - f2: (...args: In2) => Out2, - label = 'expect equal results' -) { - let generators = from.map((spec) => spec.rng); - let assertEqual = to.assertEqual ?? deepEqual; - test(...(generators as any[]), (...args) => { - args.pop(); - let inputs = args as any as In1; - handleErrors( - () => f1(...inputs), - () => - to.back(f2(...(inputs.map((x, i) => from[i].there(x)) as any as In2))), - (x, y) => assertEqual(x, y, label), - label - ); - }); -} - function id(x: T) { return x; } @@ -111,7 +90,36 @@ function toUnion(spec: OrUnion): FromSpecUnion { return specAny._isUnion ? specAny : oneOf(specAny); } -// equivalence in provable code +// equivalence tester + +function equivalent< + In extends Tuple>, + Out extends ToSpec +>({ from, to }: { from: In; to: Out }) { + return function run( + f1: (...args: Params1) => Result1, + f2: (...args: Params2) => Result2, + label = 'expect equal results' + ) { + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + test(...(generators as any[]), (...args) => { + args.pop(); + let inputs = args as Params1; + handleErrors( + () => f1(...inputs), + () => + to.back( + f2(...(inputs.map((x, i) => from[i].there(x)) as Params2)) + ), + (x, y) => assertEqual(x, y, label), + label + ); + }); + }; +} + +// equivalence tester for provable code function equivalentProvable< In extends Tuple>, @@ -180,7 +188,7 @@ let bigintField: Spec = { back: id, }; -let bool: Spec = { +let bool: ProvableSpec = { rng: Random.boolean, there: Bool, back: (x) => x.toBoolean(), From f0103e6dfacefa3c6ec02f28def13a14b2f2a901 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:40:07 +0200 Subject: [PATCH 0170/1215] equivalence testing for async functions --- src/lib/testing/equivalent.ts | 70 ++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 77cd08634f..18a29f4a26 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -9,13 +9,14 @@ import { Bool, Field } from '../core.js'; export { equivalent, equivalentProvable, + equivalentAsync, oneOf, throwError, handleErrors, deepEqual as defaultAssertEqual, id, }; -export { field, bigintField, bool, unit }; +export { field, bigintField, bool, boolean, unit }; export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; // a `Spec` tells us how to compare two functions @@ -119,6 +120,38 @@ function equivalent< }; } +// async equivalence + +function equivalentAsync< + In extends Tuple>, + Out extends ToSpec +>({ from, to }: { from: In; to: Out }, { runs = 1 } = {}) { + return async function run( + f1: (...args: Params1) => Promise> | Result1, + f2: (...args: Params2) => Promise> | Result2, + label = 'expect equal results' + ) { + let generators = from.map((spec) => spec.rng); + let assertEqual = to.assertEqual ?? deepEqual; + + let nexts = generators.map((g) => g.create()); + + for (let i = 0; i < runs; i++) { + let args = nexts.map((next) => next()); + let inputs = args as Params1; + await handleErrorsAsync( + () => f1(...inputs), + async () => + to.back( + await f2(...(inputs.map((x, i) => from[i].there(x)) as Params2)) + ), + (x, y) => assertEqual(x, y, label), + label + ); + } + }; +} + // equivalence tester for provable code function equivalentProvable< @@ -194,6 +227,11 @@ let bool: ProvableSpec = { back: (x) => x.toBoolean(), provable: Bool, }; +let boolean: Spec = { + rng: Random.boolean, + there: id, + back: id, +}; // helper to ensure two functions throw equivalent errors @@ -227,6 +265,36 @@ function handleErrors( } } +async function handleErrorsAsync( + op1: () => T, + op2: () => S, + useResults?: (a: Awaited, b: Awaited) => R, + label?: string +): Promise { + let result1: Awaited, result2: Awaited; + let error1: Error | undefined; + let error2: Error | undefined; + try { + result1 = await op1(); + } catch (err) { + error1 = err as Error; + } + try { + result2 = await op2(); + } catch (err) { + error2 = err as Error; + } + if (!!error1 !== !!error2) { + error1 && console.log(error1); + error2 && console.log(error2); + } + let message = `${(label && `${label}: `) || ''}equivalent errors`; + deepEqual(!!error1, !!error2, message); + if (!(error1 || error2) && useResults !== undefined) { + return useResults(result1!, result2!); + } +} + function throwError(message?: string): any { throw Error(message); } From 7a3cdb21f556c8bfe33817e9faccd8770dfd5bd5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:40:43 +0200 Subject: [PATCH 0171/1215] add test for 64-bit range check gate --- src/lib/gadgets/gadgets.unit-test.ts | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/lib/gadgets/gadgets.unit-test.ts diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts new file mode 100644 index 0000000000..9963d53424 --- /dev/null +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -0,0 +1,42 @@ +import { Field } from '../field.js'; +import { ZkProgram } from '../proof_system.js'; +import { + Spec, + boolean, + equivalentAsync, + field, +} from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { rangeCheck64 } from './range-check.js'; + +let RangeCheck64 = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(x) { + rangeCheck64(x); + }, + }, + }, +}); + +await RangeCheck64.compile(); + +let maybeUint64: Spec = { + ...field, + rng: Random.oneOf(Random.uint64, Random.uint64.invalid), +}; + +// do a couple of proofs +// TODO: we use this as a test because there's no way to check custom gates quickly :( + +equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( + (x) => { + if (x >= 1n << 64n) throw Error('expected 64 bits'); + return true; + }, + async (x) => { + let proof = await RangeCheck64.run(x); + return await RangeCheck64.verify(proof); + } +); From 40ca430f62a425da6b43f94a732c862c532ce27f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:46:24 +0200 Subject: [PATCH 0172/1215] export range check 64 gate under Gadgets namespace --- src/index.ts | 1 + src/lib/gadgets/gadgets.ts | 7 +++++++ src/lib/gadgets/gadgets.unit-test.ts | 6 ++++-- src/lib/gadgets/range-check.ts | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/lib/gadgets/gadgets.ts diff --git a/src/index.ts b/src/index.ts index 0813d3da4d..8a2f6ab2a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ export { export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; +export { Gadgets } from './lib/gadgets/gadgets.js'; export { Types } from './bindings/mina-transaction/types.js'; export * as Mina from './lib/mina.js'; diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts new file mode 100644 index 0000000000..107ce568bf --- /dev/null +++ b/src/lib/gadgets/gadgets.ts @@ -0,0 +1,7 @@ +import { rangeCheck64 } from './range-check.js'; + +export { Gadgets }; + +const Gadgets = { + rangeCheck64, +}; diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 9963d53424..d34ed8231d 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -7,14 +7,16 @@ import { field, } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; -import { rangeCheck64 } from './range-check.js'; +import { Gadgets } from './gadgets.js'; + +// TODO: make a ZkFunction or something that doesn't go through Pickles let RangeCheck64 = ZkProgram({ methods: { run: { privateInputs: [Field], method(x) { - rangeCheck64(x); + Gadgets.rangeCheck64(x); }, }, }, diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 12a914ccd8..d244b83ec4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -3,6 +3,10 @@ import * as Gates from '../gates.js'; export { rangeCheck64 }; +/** + * Asserts that x is in the range [0, 2^64) + * @param x field element + */ function rangeCheck64(x: Field) { if (x.isConstant()) { if (x.toBigInt() >= 1n << 64n) { From 1951f571d4eab828111aab7193850c9e8e56c20e Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:46:29 +0200 Subject: [PATCH 0173/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 91e701cd02..fc7f6a97fd 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 91e701cd027d5a1213e750e5e9b80da80c9d211e +Subproject commit fc7f6a97fd5d0dd7941040f996441fe48a3b6c83 From 5e7467fe020b3ee73ffffe3c2ab5fd3f575c586d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:51:35 +0200 Subject: [PATCH 0174/1215] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55c81885b..069787f8d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - To recover existing verification keys and behavior, change the order of properties in your Struct definitions to be alphabetical - The `customObjectKeys` option is removed from `Struct` +### Added + +- `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 + ### Changed - Improve prover performance by ~25% https://github.com/o1-labs/o1js/pull/1092 - Change internal representation of field elements to be JS bigint instead of Uint8Array +- Consolidate internal framework for testing equivalence of two implementations ## [0.13.0](https://github.com/o1-labs/o1js/compare/fbd4b2717...c2f392fe5) From e5f8e37df51b10eeb891934594d3273451e57c2d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:54:32 +0200 Subject: [PATCH 0175/1215] revert using range check gate for uint64 --- src/lib/int.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 451ceb8610..5299166b9e 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -67,7 +67,8 @@ class UInt64 extends CircuitValue { } static check(x: UInt64) { - rangeCheck64(x.value); + let actual = x.value.rangeCheckHelper(64); + actual.assertEquals(x.value); } static toInput(x: UInt64): HashInput { From f5e67962a614f29fbb9459a7c7ebfa4f608c0ba1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 16:56:48 +0200 Subject: [PATCH 0176/1215] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55c81885b..b74f554fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/045faa7...HEAD) +### Added + +- Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes From a16c7e4265f0ea66a288e91e691c5d82e5a37d1a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 18:11:01 +0200 Subject: [PATCH 0177/1215] fixup unit test --- src/examples/ex02_root.ts | 3 ++- src/examples/ex02_root_program.ts | 10 ++-------- src/lib/gadgets/gadgets.unit-test.ts | 7 +++++-- src/lib/testing/equivalent.ts | 25 ++++++++++++++++--------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/examples/ex02_root.ts b/src/examples/ex02_root.ts index 31dd48e818..54e5f0841b 100644 --- a/src/examples/ex02_root.ts +++ b/src/examples/ex02_root.ts @@ -1,4 +1,4 @@ -import { Field, Circuit, circuitMain, public_, UInt64 } from 'o1js'; +import { Field, Circuit, circuitMain, public_, UInt64, Gadgets } from 'o1js'; /* Exercise 2: @@ -10,6 +10,7 @@ Prove: class Main extends Circuit { @circuitMain static main(@public_ y: Field, x: UInt64) { + Gadgets.rangeCheck64(x.value); let y3 = y.square().mul(y); y3.assertEquals(x.value); } diff --git a/src/examples/ex02_root_program.ts b/src/examples/ex02_root_program.ts index c4115dbd8d..b301a9f72f 100644 --- a/src/examples/ex02_root_program.ts +++ b/src/examples/ex02_root_program.ts @@ -1,11 +1,4 @@ -import { - Field, - Circuit, - circuitMain, - public_, - UInt64, - Experimental, -} from 'o1js'; +import { Field, UInt64, Experimental, Gadgets } from 'o1js'; let { ZkProgram } = Experimental; @@ -15,6 +8,7 @@ const Main = ZkProgram({ main: { privateInputs: [UInt64], method(y: Field, x: UInt64) { + Gadgets.rangeCheck64(x.value); let y3 = y.square().mul(y); y3.assertEquals(x.value); }, diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index d34ed8231d..6d65c57d50 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -1,3 +1,4 @@ +import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { ZkProgram } from '../proof_system.js'; import { @@ -26,13 +27,15 @@ await RangeCheck64.compile(); let maybeUint64: Spec = { ...field, - rng: Random.oneOf(Random.uint64, Random.uint64.invalid), + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), }; // do a couple of proofs // TODO: we use this as a test because there's no way to check custom gates quickly :( -equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( +equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 20 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); return true; diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 18a29f4a26..c19748624e 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -139,15 +139,22 @@ function equivalentAsync< for (let i = 0; i < runs; i++) { let args = nexts.map((next) => next()); let inputs = args as Params1; - await handleErrorsAsync( - () => f1(...inputs), - async () => - to.back( - await f2(...(inputs.map((x, i) => from[i].there(x)) as Params2)) - ), - (x, y) => assertEqual(x, y, label), - label - ); + try { + await handleErrorsAsync( + () => f1(...inputs), + async () => + to.back( + await f2( + ...(inputs.map((x, i) => from[i].there(x)) as Params2) + ) + ), + (x, y) => assertEqual(x, y, label), + label + ); + } catch (err) { + console.log(...inputs); + throw err; + } } }; } From 14a7daf5f2945c90f248717ee99c70146d2d68c7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 18:15:19 +0200 Subject: [PATCH 0178/1215] revert some more changes --- src/lib/int.ts | 1 - src/lib/proof_system.unit-test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 5299166b9e..f4143a9a5b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,7 +3,6 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { rangeCheck64 } from './gadgets/range-check.js'; // external API export { UInt32, UInt64, Int64, Sign }; diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index 7281070229..e7ec592915 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -46,4 +46,4 @@ const CounterProgram = ZkProgram({ }); const incrementMethodMetadata = CounterProgram.analyzeMethods()[0]; -expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 9 })); +expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 18 })); From ce21d6b9ba2f0e3bccd146b16c75e49a8838ecc5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 12 Oct 2023 18:16:52 +0200 Subject: [PATCH 0179/1215] fixup changelog --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b6a31c849..1e64b6d357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 +- `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes @@ -37,10 +39,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - To recover existing verification keys and behavior, change the order of properties in your Struct definitions to be alphabetical - The `customObjectKeys` option is removed from `Struct` -### Added - -- `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 - ### Changed - Improve prover performance by ~25% https://github.com/o1-labs/o1js/pull/1092 From f3bfc03f8617e58303d286b9941f45037526acc9 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 22:59:04 +0200 Subject: [PATCH 0180/1215] initial refactor --- src/lib/gadgets/bitwise.ts | 192 +++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 106 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 5090fefcd2..b16199a1fb 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,15 +1,16 @@ -import { Snarky } from 'src/snarky.js'; -import { Field, FieldConst, FieldVar } from '../field.js'; +import { Field } from '../field.js'; import { Provable } from '../provable.js'; import * as Gates from '../gates.js'; +export { xor }; + /** * Boolean Xor of length bits * input1 and input2 are the inputs to the Xor gate * length is the number of bits to Xor * len_xor is the number of bits of the lookup table (default is 4) */ -function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { +function xor(input1: Field, input2: Field, length: number, len_xor = 4) { // check that both input lengths are positive assert( length > 0 && len_xor < 0, @@ -26,107 +27,6 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { throw Error('TODO constant case'); } - // calculates next variable for the chain as provr - function nextVariable(): Field { - return new Field(5); - } - - // builds xor chain recurisvely - function xorRec( - input1: Field, - input2: Field, - outputXor: FieldVar, - padLength: number, - len_xor: number - ) { - // if inputs are zero and length is zero, add the zero check - - if (padLength == 0) { - Gates.zeroCheck(input1, input2, new Field(outputXor)); - - let zero = new Field(0); - zero.assertEquals(input1); - zero.assertEquals(input2); - zero.assertEquals(new Field(outputXor)); - } else { - function ofBits(f: Field, start: number, stop: number) { - if (stop !== -1 && stop <= start) - throw Error('Stop offste must be greater than start offset'); - - return Provable.witness(Field, () => - fieldBitsToFieldLE(f, start, stop) - ); - } - - // nibble offsets - let first = len_xor; - let second = first + len_xor; - let third = second + len_xor; - let fourth = third + len_xor; - - let in1_0 = ofBits(input1, 0, first); - let in1_1 = ofBits(input1, first, second); - let in1_2 = ofBits(input1, second, third); - let in1_3 = ofBits(input1, third, fourth); - let in2_0 = ofBits(input2, 0, first); - let in2_1 = ofBits(input2, first, second); - let in2_2 = ofBits(input2, second, third); - let in2_3 = ofBits(input2, third, fourth); - let out_0 = ofBits(new Field(outputXor), 0, first); - let out_1 = ofBits(new Field(outputXor), first, second); - let out_2 = ofBits(new Field(outputXor), second, third); - let out_3 = ofBits(new Field(outputXor), third, fourth); - - Gates.xor( - input1, - input2, - new Field(outputXor), - in1_0, - in1_1, - in1_2, - in1_3, - in2_0, - in2_1, - in2_2, - in2_3, - out_0, - out_1, - out_2, - out_3 - ); - - let next_in1 = asProverNextVar( - input1, - in1_0, - in1_1, - in1_2, - in1_3, - len_xor - ); - - let next_in2 = asProverNextVar( - input2, - in2_0, - in2_1, - in2_2, - in2_3, - len_xor - ); - - let next_out = asProverNextVar( - new Field(outputXor), - out_0, - out_1, - out_2, - out_3, - len_xor - ); - - let next_length = padLength - 4 * len_xor; - xorRec(next_in1, next_in2, next_out.value, next_length, len_xor); - } - } - // create two input arrays of length 255 set to false let input1Array = new Array(length).fill(false); let input2Array = new Array(length).fill(false); @@ -148,7 +48,7 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { } }); - let outputXor = Snarky.existsVar(() => { + let outputXor = Provable.witness(Field, () => { // check real lengths are at most the desired length fitsInBits(input1, length); fitsInBits(input2, length); @@ -164,7 +64,7 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { }); // convert bits to Field - return FieldConst.fromBigint(Field.fromBits(outputBits).toBigInt()); + return Field.fromBits(outputBits); }); // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table @@ -181,6 +81,86 @@ function bxor(input1: Field, input2: Field, length: number, len_xor = 4) { return new Field(outputXor); } +// builds xor chain recurisvely +function xorRec( + input1: Field, + input2: Field, + outputXor: Field, + padLength: number, + len_xor: number +) { + // if inputs are zero and length is zero, add the zero check + + if (padLength == 0) { + Gates.zeroCheck(input1, input2, outputXor); + + let zero = new Field(0); + zero.assertEquals(input1); + zero.assertEquals(input2); + zero.assertEquals(outputXor); + } else { + function ofBits(f: Field, start: number, stop: number) { + if (stop !== -1 && stop <= start) + throw Error('Stop offste must be greater than start offset'); + + return Provable.witness(Field, () => fieldBitsToFieldLE(f, start, stop)); + } + + // nibble offsets + let first = len_xor; + let second = first + len_xor; + let third = second + len_xor; + let fourth = third + len_xor; + + let in1_0 = ofBits(input1, 0, first); + let in1_1 = ofBits(input1, first, second); + let in1_2 = ofBits(input1, second, third); + let in1_3 = ofBits(input1, third, fourth); + let in2_0 = ofBits(input2, 0, first); + let in2_1 = ofBits(input2, first, second); + let in2_2 = ofBits(input2, second, third); + let in2_3 = ofBits(input2, third, fourth); + let out_0 = ofBits(outputXor, 0, first); + let out_1 = ofBits(outputXor, first, second); + let out_2 = ofBits(outputXor, second, third); + let out_3 = ofBits(outputXor, third, fourth); + + Gates.xor( + input1, + input2, + outputXor, + in1_0, + in1_1, + in1_2, + in1_3, + in2_0, + in2_1, + in2_2, + in2_3, + out_0, + out_1, + out_2, + out_3 + ); + + let next_in1 = asProverNextVar(input1, in1_0, in1_1, in1_2, in1_3, len_xor); + + let next_in2 = asProverNextVar(input2, in2_0, in2_1, in2_2, in2_3, len_xor); + + let next_out = asProverNextVar( + outputXor, + out_0, + out_1, + out_2, + out_3, + len_xor + ); + + let next_length = padLength - 4 * len_xor; + xorRec(next_in1, next_in2, next_out, next_length, len_xor); + } +} + function assert(stmt: boolean, message?: string) { if (!stmt) { throw Error(message ?? 'Assertion failed'); From 77cd4ae35e4ffd42f1c06ca58b17319aa6bc3897 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 23:37:33 +0200 Subject: [PATCH 0181/1215] add basic example, dummy example --- src/examples/gadgets.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/examples/gadgets.ts diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts new file mode 100644 index 0000000000..6574e047c7 --- /dev/null +++ b/src/examples/gadgets.ts @@ -0,0 +1,41 @@ +import { Field, Provable, xor, Experimental } from 'o1js'; + +let cs = Provable.constraintSystem(() => { + let res = xor( + Provable.witness(Field, () => Field(5215)), + Provable.witness(Field, () => Field(7812)), + 4 + ); + Provable.log(res); +}); +console.log(cs); + +const XOR = Experimental.ZkProgram({ + methods: { + baseCase: { + privateInputs: [], + method: () => { + let a = Provable.witness(Field, () => Field(5)); + let b = Provable.witness(Field, () => Field(2)); + let actual = xor(a, b, 4); + let expected = Field(7); + actual.assertEquals(expected); + }, + }, + }, +}); + +console.log('compiling..'); + +console.time('compile'); +await XOR.compile(); +console.timeEnd('compile'); + +console.log('proving..'); + +console.time('prove'); +let proof = await XOR.baseCase(); +console.timeEnd('prove'); + +if (!(await XOR.verify(proof))) throw Error('Invalid proof'); +else console.log('proof valid'); From 03c138a040a911e851c2245a0d74ee9c7aa72412 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 23:37:53 +0200 Subject: [PATCH 0182/1215] make proving work, temporary --- src/bindings | 2 +- src/index.ts | 2 ++ src/lib/gadgets/bitwise.ts | 29 ++++++++++++++--------------- src/lib/gates.ts | 1 + 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/bindings b/src/bindings index b040bde6d8..8483c264a9 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b040bde6d8afcb2514aadcf30e80001b4f9b6222 +Subproject commit 8483c264a9be6cb268c80eff507fe808d6204ae7 diff --git a/src/index.ts b/src/index.ts index 0813d3da4d..eb11633692 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +export { xor } from './lib/gadgets/bitwise.js'; + export type { ProvablePure } from './snarky.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index b16199a1fb..eb9d907605 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -10,10 +10,10 @@ export { xor }; * length is the number of bits to Xor * len_xor is the number of bits of the lookup table (default is 4) */ -function xor(input1: Field, input2: Field, length: number, len_xor = 4) { +function xor(a: Field, b: Field, length: number, len_xor = 4) { // check that both input lengths are positive assert( - length > 0 && len_xor < 0, + length > 0 && len_xor > 0, `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); @@ -23,10 +23,10 @@ function xor(input1: Field, input2: Field, length: number, len_xor = 4) { 'Length exceeds maximum field size in bits.' ); - if (input1.isConstant() && input2.isConstant()) { + /* if (a.isConstant() && b.isConstant()) { throw Error('TODO constant case'); } - + */ // create two input arrays of length 255 set to false let input1Array = new Array(length).fill(false); let input2Array = new Array(length).fill(false); @@ -34,12 +34,12 @@ function xor(input1: Field, input2: Field, length: number, len_xor = 4) { // sanity check as prover about lengths of inputs Provable.asProver(() => { // check real lengths are at most the desired length - fitsInBits(input1, length); - fitsInBits(input2, length); + fitsInBits(a, length); + fitsInBits(b, length); // converts input field elements to list of bits of length 255 - let input1Bits = input1.toBits(); - let input2Bits = input2.toBits(); + let input1Bits = a.toBits(); + let input2Bits = b.toBits(); // iterate over 255 positions to update value of arrays for (let i = 0; i < Field.sizeInBits() - 1; i++) { @@ -50,12 +50,12 @@ function xor(input1: Field, input2: Field, length: number, len_xor = 4) { let outputXor = Provable.witness(Field, () => { // check real lengths are at most the desired length - fitsInBits(input1, length); - fitsInBits(input2, length); + fitsInBits(a, length); + fitsInBits(b, length); // converts input field elements to list of bits of length 255 - let input1Bits = input1.toBits(); - let input2Bits = input2.toBits(); + let input1Bits = a.toBits(); + let input2Bits = b.toBits(); let outputBits = input1Bits.map((bit, i) => { let b1 = bit; @@ -75,10 +75,9 @@ function xor(input1: Field, input2: Field, length: number, len_xor = 4) { // recurisvely build xor gadget - xorRec(input1, input2, outputXor, padLength, len_xor); - + xorRec(a, b, outputXor, padLength, len_xor); // convert back to field - return new Field(outputXor); + return outputXor; } // builds xor chain recurisvely diff --git a/src/lib/gates.ts b/src/lib/gates.ts index a585dbf2ef..f20dfbb08c 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -62,6 +62,7 @@ function xor( out_2: Field, out_3: Field ) { + console.log('XOR'); Snarky.gates.xor( input1.value, input2.value, From 4afed255dc7d6522a3e9d4be9b8f98177a18ff50 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 12 Oct 2023 14:54:06 -0700 Subject: [PATCH 0183/1215] chore(bindings): update submodule for rot gate --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a6b9460a6f..1f7443d077 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a6b9460a6f113bcf82912b6f75efbf9842a4b356 +Subproject commit 1f7443d0776382bf39bf13ea0becd59712abaf2f From 6bdcbbffe90d9aa997911ed5b651e7cb4af9a618 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 12 Oct 2023 14:54:26 -0700 Subject: [PATCH 0184/1215] feat(snarky.d.ts): add 'rot' function to Snarky interface to support rotation operations --- src/snarky.d.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 7a0f16f129..1b3cdec993 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -298,6 +298,25 @@ declare const Snarky: { ], compact: FieldConst ): void; + + rot( + word: FieldVar, + rotated: FieldVar, + excess: FieldVar, + limbs: [0, FieldVar, FieldVar, FieldVar, FieldVar], + crumbs: [ + 0, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar + ], + two_to_rot: FieldConst + ): void; }; bool: { From 957cd54ee6ab1a6e48091d5a7480562b6a1ce27e Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 12 Oct 2023 23:56:27 +0200 Subject: [PATCH 0185/1215] fix stackoverflow --- src/lib/gadgets/bitwise.ts | 42 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index eb9d907605..51ad061e1c 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -68,7 +68,7 @@ function xor(a: Field, b: Field, length: number, len_xor = 4) { }); // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table - let padLength = len_xor; + let padLength = length; if (length % (4 * len_xor) !== 0) { padLength = length + 4 * len_xor - (length % (4 * len_xor)); } @@ -80,22 +80,26 @@ function xor(a: Field, b: Field, length: number, len_xor = 4) { return outputXor; } +let aa = 0; // builds xor chain recurisvely function xorRec( - input1: Field, - input2: Field, + a: Field, + b: Field, outputXor: Field, padLength: number, len_xor: number ) { // if inputs are zero and length is zero, add the zero check + console.log('padLength', padLength); + aa++; - if (padLength == 0) { - Gates.zeroCheck(input1, input2, outputXor); + if (aa >= 30) throw Error('too many calls'); + if (padLength === 0) { + Gates.zeroCheck(a, b, outputXor); let zero = new Field(0); - zero.assertEquals(input1); - zero.assertEquals(input2); + zero.assertEquals(a); + zero.assertEquals(b); zero.assertEquals(outputXor); } else { function ofBits(f: Field, start: number, stop: number) { @@ -111,22 +115,22 @@ function xorRec( let third = second + len_xor; let fourth = third + len_xor; - let in1_0 = ofBits(input1, 0, first); - let in1_1 = ofBits(input1, first, second); - let in1_2 = ofBits(input1, second, third); - let in1_3 = ofBits(input1, third, fourth); - let in2_0 = ofBits(input2, 0, first); - let in2_1 = ofBits(input2, first, second); - let in2_2 = ofBits(input2, second, third); - let in2_3 = ofBits(input2, third, fourth); + let in1_0 = ofBits(a, 0, first); + let in1_1 = ofBits(a, first, second); + let in1_2 = ofBits(a, second, third); + let in1_3 = ofBits(a, third, fourth); + let in2_0 = ofBits(b, 0, first); + let in2_1 = ofBits(b, first, second); + let in2_2 = ofBits(b, second, third); + let in2_3 = ofBits(b, third, fourth); let out_0 = ofBits(outputXor, 0, first); let out_1 = ofBits(outputXor, first, second); let out_2 = ofBits(outputXor, second, third); let out_3 = ofBits(outputXor, third, fourth); Gates.xor( - input1, - input2, + a, + b, outputXor, in1_0, in1_1, @@ -142,9 +146,9 @@ function xorRec( out_3 ); - let next_in1 = asProverNextVar(input1, in1_0, in1_1, in1_2, in1_3, len_xor); + let next_in1 = asProverNextVar(a, in1_0, in1_1, in1_2, in1_3, len_xor); - let next_in2 = asProverNextVar(input2, in2_0, in2_1, in2_2, in2_3, len_xor); + let next_in2 = asProverNextVar(b, in2_0, in2_1, in2_2, in2_3, len_xor); let next_out = asProverNextVar( outputXor, From 24acc09bad4479d498b33164849c0f5b298631ea Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:21:29 +0200 Subject: [PATCH 0186/1215] more example code --- src/examples/gadgets.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 6574e047c7..18e8fb28f2 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,10 +1,22 @@ import { Field, Provable, xor, Experimental } from 'o1js'; +Provable.runAndCheck(() => { + let res = xor( + Field(5215), + Provable.witness(Field, () => Field(7812)), + 16 + ); + Provable.log(res); +}); + +let res = xor(Field(2), Field(5), 4); +Provable.log(res); + let cs = Provable.constraintSystem(() => { let res = xor( Provable.witness(Field, () => Field(5215)), Provable.witness(Field, () => Field(7812)), - 4 + 2 ); Provable.log(res); }); From a1b5c943ecc3dd793fb3f0f743a37fe8e9d65851 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:21:44 +0200 Subject: [PATCH 0187/1215] expose helper --- src/lib/field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/field.ts b/src/lib/field.ts index 16e79c802b..fc94ed40c5 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -19,6 +19,7 @@ export { withMessage, readVarMessage, toConstantField, + toFp, }; type FieldConst = [0, bigint]; From 89aebb7aa17dfeb9bf74264458297d188f55a765 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:21:54 +0200 Subject: [PATCH 0188/1215] simplify --- src/lib/gadgets/bitwise.ts | 90 +++++++++++++++----------------------- 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 51ad061e1c..9135c7e3e6 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,71 +1,55 @@ -import { Field } from '../field.js'; +import { Field, toFp } from '../field.js'; import { Provable } from '../provable.js'; +import { Field as Fp } from '../../provable/field-bigint.js'; import * as Gates from '../gates.js'; export { xor }; /** - * Boolean Xor of length bits - * input1 and input2 are the inputs to the Xor gate - * length is the number of bits to Xor - * len_xor is the number of bits of the lookup table (default is 4) + * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * The `length` parameter lets you define how many bits should be compared. By default it is set to `32`. The output is not constrained to the length. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. + * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. + * + * ```typescript + * let a = Field(5); // ... 000101 + * let b = Field(3); // ... 000011 + * + * let c = a.xor(b); // ... 000110 + * c.assertEquals(6); + * ``` */ function xor(a: Field, b: Field, length: number, len_xor = 4) { // check that both input lengths are positive assert( length > 0 && len_xor > 0, - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + `Input lengths need to be positive values.` ); // check that length does not exceed maximum field size in bits assert( length <= Field.sizeInBits(), - 'Length exceeds maximum field size in bits.' + `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); - /* if (a.isConstant() && b.isConstant()) { - throw Error('TODO constant case'); - } - */ - // create two input arrays of length 255 set to false - let input1Array = new Array(length).fill(false); - let input2Array = new Array(length).fill(false); - - // sanity check as prover about lengths of inputs - Provable.asProver(() => { - // check real lengths are at most the desired length - fitsInBits(a, length); - fitsInBits(b, length); - - // converts input field elements to list of bits of length 255 - let input1Bits = a.toBits(); - let input2Bits = b.toBits(); - - // iterate over 255 positions to update value of arrays - for (let i = 0; i < Field.sizeInBits() - 1; i++) { - input1Array[i] = input1Bits[i]; - input2Array[i] = input2Bits[i]; - } - }); + // check that both elements fit into length bits as prover + fitsInBits(a, length); + fitsInBits(b, length); - let outputXor = Provable.witness(Field, () => { - // check real lengths are at most the desired length - fitsInBits(a, length); - fitsInBits(b, length); - - // converts input field elements to list of bits of length 255 - let input1Bits = a.toBits(); - let input2Bits = b.toBits(); - - let outputBits = input1Bits.map((bit, i) => { - let b1 = bit; - let b2 = input2Bits[i]; - return b1.equals(b2).not(); - }); + // handle constant case + if (a.isConstant() && b.isConstant()) { + return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); + } - // convert bits to Field - return Field.fromBits(outputBits); - }); + let outputXor = Provable.witness( + Field, + () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) + ); // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table let padLength = length; @@ -80,7 +64,6 @@ function xor(a: Field, b: Field, length: number, len_xor = 4) { return outputXor; } -let aa = 0; // builds xor chain recurisvely function xorRec( a: Field, @@ -90,10 +73,6 @@ function xorRec( len_xor: number ) { // if inputs are zero and length is zero, add the zero check - console.log('padLength', padLength); - aa++; - - if (aa >= 30) throw Error('too many calls'); if (padLength === 0) { Gates.zeroCheck(a, b, outputXor); @@ -172,7 +151,10 @@ function assert(stmt: boolean, message?: string) { function fitsInBits(word: Field, length: number) { Provable.asProver(() => { - assert(word.toBigInt() < 2 ** length, 'Word does not fit in bits'); + assert( + word.toBigInt() < 2 ** length, + `${word.toBigInt()} does not fit into ${length} bits` + ); }); } From f9def7694a0358c0c60f829e03cef7c77b8865c5 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:22:06 +0200 Subject: [PATCH 0189/1215] remove debug code --- src/lib/gates.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index f20dfbb08c..a585dbf2ef 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -62,7 +62,6 @@ function xor( out_2: Field, out_3: Field ) { - console.log('XOR'); Snarky.gates.xor( input1.value, input2.value, From 823542451be5a1d98722c5fc7a8a58da5fff4d70 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:22:29 +0200 Subject: [PATCH 0190/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 8483c264a9..4aa3fc8835 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 8483c264a9be6cb268c80eff507fe808d6204ae7 +Subproject commit 4aa3fc883539ea41aca0d37d6c8b86aacbd1e8d5 From 6d79928a546a1a57ffc834ecbc5b45756107f3f1 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:43:37 +0200 Subject: [PATCH 0191/1215] simplify more --- src/lib/gadgets/bitwise.ts | 81 +++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 9135c7e3e6..8bbd65555e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -9,7 +9,7 @@ export { xor }; * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. * - * The `length` parameter lets you define how many bits should be compared. By default it is set to `32`. The output is not constrained to the length. + * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. * * **Note:** Specifying a larger `length` parameter adds additional constraints. * @@ -20,14 +20,14 @@ export { xor }; * let a = Field(5); // ... 000101 * let b = Field(3); // ... 000011 * - * let c = a.xor(b); // ... 000110 + * let c = xor(a, b, 2); // ... 000110 * c.assertEquals(6); * ``` */ -function xor(a: Field, b: Field, length: number, len_xor = 4) { +function xor(a: Field, b: Field, length: number, lengthXor = 4) { // check that both input lengths are positive assert( - length > 0 && len_xor > 0, + length > 0 && lengthXor > 0, `Input lengths need to be positive values.` ); @@ -53,13 +53,13 @@ function xor(a: Field, b: Field, length: number, len_xor = 4) { // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table let padLength = length; - if (length % (4 * len_xor) !== 0) { - padLength = length + 4 * len_xor - (length % (4 * len_xor)); + if (length % (4 * lengthXor) !== 0) { + padLength = length + 4 * lengthXor - (length % (4 * lengthXor)); } // recurisvely build xor gadget - xorRec(a, b, outputXor, padLength, len_xor); + xorRec(a, b, outputXor, padLength, lengthXor); // convert back to field return outputXor; } @@ -70,7 +70,7 @@ function xorRec( b: Field, outputXor: Field, padLength: number, - len_xor: number + lengthXor: number ) { // if inputs are zero and length is zero, add the zero check if (padLength === 0) { @@ -81,31 +81,24 @@ function xorRec( zero.assertEquals(b); zero.assertEquals(outputXor); } else { - function ofBits(f: Field, start: number, stop: number) { - if (stop !== -1 && stop <= start) - throw Error('Stop offste must be greater than start offset'); - - return Provable.witness(Field, () => fieldBitsToFieldLE(f, start, stop)); - } - // nibble offsets - let first = len_xor; - let second = first + len_xor; - let third = second + len_xor; - let fourth = third + len_xor; - - let in1_0 = ofBits(a, 0, first); - let in1_1 = ofBits(a, first, second); - let in1_2 = ofBits(a, second, third); - let in1_3 = ofBits(a, third, fourth); - let in2_0 = ofBits(b, 0, first); - let in2_1 = ofBits(b, first, second); - let in2_2 = ofBits(b, second, third); - let in2_3 = ofBits(b, third, fourth); - let out_0 = ofBits(outputXor, 0, first); - let out_1 = ofBits(outputXor, first, second); - let out_2 = ofBits(outputXor, second, third); - let out_3 = ofBits(outputXor, third, fourth); + let first = lengthXor; + let second = first + lengthXor; + let third = second + lengthXor; + let fourth = third + lengthXor; + + let in1_0 = sliceBits(a, 0, first); + let in1_1 = sliceBits(a, first, second); + let in1_2 = sliceBits(a, second, third); + let in1_3 = sliceBits(a, third, fourth); + let in2_0 = sliceBits(b, 0, first); + let in2_1 = sliceBits(b, first, second); + let in2_2 = sliceBits(b, second, third); + let in2_3 = sliceBits(b, third, fourth); + let out_0 = sliceBits(outputXor, 0, first); + let out_1 = sliceBits(outputXor, first, second); + let out_2 = sliceBits(outputXor, second, third); + let out_3 = sliceBits(outputXor, third, fourth); Gates.xor( a, @@ -125,9 +118,9 @@ function xorRec( out_3 ); - let next_in1 = asProverNextVar(a, in1_0, in1_1, in1_2, in1_3, len_xor); + let next_in1 = asProverNextVar(a, in1_0, in1_1, in1_2, in1_3, lengthXor); - let next_in2 = asProverNextVar(b, in2_0, in2_1, in2_2, in2_3, len_xor); + let next_in2 = asProverNextVar(b, in2_0, in2_1, in2_2, in2_3, lengthXor); let next_out = asProverNextVar( outputXor, @@ -135,11 +128,11 @@ function xorRec( out_1, out_2, out_3, - len_xor + lengthXor ); - let next_length = padLength - 4 * len_xor; - xorRec(next_in1, next_in2, next_out, next_length, len_xor); + let next_length = padLength - 4 * lengthXor; + xorRec(next_in1, next_in2, next_out, next_length, lengthXor); } } @@ -158,17 +151,17 @@ function fitsInBits(word: Field, length: number) { }); } -function fieldBitsToFieldLE(f: Field, start: number, stop: number) { +function sliceBits(f: Field, start: number, stop = -1) { if (stop !== -1 && stop <= start) throw Error('stop offset must be greater than start offset'); - let bits = f.toBits(); + return Provable.witness(Field, () => { + let bits = f.toBits(); + if (stop > bits.length) throw Error('stop must be less than bit-length'); + if (stop === -1) stop = bits.length; - if (stop > bits.length) throw Error('stop must be less than bit-length'); - - if (stop === -1) stop = bits.length; - - return Field.fromBits(bits.slice(start, stop)); + return Field.fromBits(bits.slice(start, stop)); + }); } function asProverNextVar( From ea423df02373aa31a5009ce673bd60fef063733e Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:55:09 +0200 Subject: [PATCH 0192/1215] integrate API surface --- src/lib/field.ts | 24 ++++++++++++++++++++++++ src/lib/int.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/lib/field.ts b/src/lib/field.ts index fc94ed40c5..50f402aa8b 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -5,6 +5,7 @@ import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; +import * as Gadgets from './gadgets/bitwise.js'; // external API export { Field }; @@ -589,6 +590,29 @@ class Field { return new Field(z); } + /** + * Bitwise XOR gate on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * The `length` parameter lets you define how many bits should be compared. By default it is set to `32`. The output is not constrained to the length. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. + * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. + * + * ```typescript + * let a = Field(5); // ... 000101 + * let b = Field(3); // ... 000011 + * + * let c = a.xor(b); // ... 000110 + * c.assertEquals(6); + * ``` + */ + xor(y: Field | bigint | number | string, length: number = 32) { + return Gadgets.xor(this, Field.from(y), length); + } + /** * @deprecated use `x.equals(0)` which is equivalent */ diff --git a/src/lib/int.ts b/src/lib/int.ts index 451ceb8610..eb15455bea 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -115,6 +115,21 @@ class UInt64 extends CircuitValue { return new UInt64(Field((1n << 64n) - 1n)); } + /** + * Bitwise XOR gate on {@link UInt64} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. It applies XOR to all 64 bits of the elements. + * ```typescript + * let a = UInt64.from(5); // ... 000101 + * let b = UInt64.from(3); // ... 000011 + * + * let c = a.xor(b); // ... 000110 + * c.assertEquals(6); + * ``` + */ + xor(y: UInt64) { + return new UInt64(this.value.xor(y.value, UInt64.NUM_BITS)); + } + /** * Integer division with remainder. * @@ -458,6 +473,22 @@ class UInt32 extends CircuitValue { static MAXINT() { return new UInt32(Field((1n << 32n) - 1n)); } + + /** + * Bitwise XOR gate on {@link UInt32} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. It applies XOR to all 32 bits of the elements. + * ```typescript + * let a = UInt32.from(5); // ... 000101 + * let b = UInt32.from(3); // ... 000011 + * + * let c = a.xor(b); // ... 000110 + * c.assertEquals(6); + * ``` + */ + xor(y: UInt32) { + return new UInt32(this.value.xor(y.value, UInt32.NUM_BITS)); + } + /** * Integer division with remainder. * From bb61d3ced28caa9878d698b218b97bc531a1f526 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 00:55:20 +0200 Subject: [PATCH 0193/1215] basic unit tests --- src/lib/field.unit-test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index f28f688bb3..f4b569c60f 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -56,6 +56,23 @@ test(Random.field, Random.int(-5, 5), (x, k) => { deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); }); +// XOR with some common and odd lengths +[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { + test(Random.field, Random.field, (x_, y_, assert) => { + let modulus = 1n << BigInt(length); + let x = x_ % modulus; + let y = y_ % modulus; + let z = Field(x); + + let r1 = Fp.xor(BigInt(x), BigInt(y)); + + Provable.runAndCheck(() => { + let r2 = Provable.witness(Field, () => z).xor(y, length); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + }); +}); + // special generator let SmallField = Random.reject( Random.field, From 6f081c94891821871bec4982459205f33b2c4687 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 01:08:16 +0200 Subject: [PATCH 0194/1215] minor --- src/lib/gadgets/bitwise.ts | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 8bbd65555e..c556e8deb3 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -57,15 +57,14 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { padLength = length + 4 * lengthXor - (length % (4 * lengthXor)); } - // recurisvely build xor gadget + // recursively build xor gadget + buildXor(a, b, outputXor, padLength, lengthXor); - xorRec(a, b, outputXor, padLength, lengthXor); - // convert back to field return outputXor; } -// builds xor chain recurisvely -function xorRec( +// builds xor chain recursively +function buildXor( a: Field, b: Field, outputXor: Field, @@ -118,11 +117,9 @@ function xorRec( out_3 ); - let next_in1 = asProverNextVar(a, in1_0, in1_1, in1_2, in1_3, lengthXor); - - let next_in2 = asProverNextVar(b, in2_0, in2_1, in2_2, in2_3, lengthXor); - - let next_out = asProverNextVar( + let nextIn1 = witnessNextValue(a, in1_0, in1_1, in1_2, in1_3, lengthXor); + let nextIn2 = witnessNextValue(b, in2_0, in2_1, in2_2, in2_3, lengthXor); + let nextOut = witnessNextValue( outputXor, out_0, out_1, @@ -132,7 +129,7 @@ function xorRec( ); let next_length = padLength - 4 * lengthXor; - xorRec(next_in1, next_in2, next_out, next_length, lengthXor); + buildXor(nextIn1, nextIn2, nextOut, next_length, lengthXor); } } @@ -164,21 +161,20 @@ function sliceBits(f: Field, start: number, stop = -1) { }); } -function asProverNextVar( +function witnessNextValue( current: Field, var0: Field, var1: Field, var2: Field, var3: Field, - len_xor: number + lenXor: number ) { - let twoPowLen = new Field(2 ** len_xor); - - let twoPow2Len = twoPowLen.mul(twoPowLen); - let twoPow3Len = twoPow2Len.mul(twoPowLen); - let twoPow4Len = twoPow3Len.mul(twoPowLen); + return Provable.witness(Field, () => { + let twoPowLen = new Field(2 ** lenXor); + let twoPow2Len = twoPowLen.mul(twoPowLen); + let twoPow3Len = twoPow2Len.mul(twoPowLen); + let twoPow4Len = twoPow3Len.mul(twoPowLen); - let nextVar = Provable.witness(Field, () => { return current .sub(var0) .sub(var1.mul(twoPowLen)) @@ -186,6 +182,4 @@ function asProverNextVar( .sub(var3.mul(twoPow3Len)) .div(twoPow4Len); }); - - return nextVar; } From cd1cbbe60d44fe5ac6484d22f469993c5a00be6c Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 13 Oct 2023 01:09:34 +0200 Subject: [PATCH 0195/1215] minor --- src/lib/gates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index a585dbf2ef..4bcedf1ec5 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -81,8 +81,8 @@ function xor( ); } -function zeroCheck(in1: Field, in2: Field, out: Field) { - Snarky.gates.zeroCheck(in1.value, in2.value, out.value); +function zeroCheck(a: Field, b: Field, c: Field) { + Snarky.gates.zeroCheck(a.value, b.value, c.value); } function getBits(x: bigint, start: number, length: number) { From 46123ac8301e35d5c0896cd4d7dc7c2bfb11ad23 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 13 Oct 2023 09:26:39 +0200 Subject: [PATCH 0196/1215] address feedback: cleaner switch statement --- src/lib/proof_system.ts | 40 +++++++++++++++------------------------- src/snarky.d.ts | 2 +- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 2313f2623f..553137d0fd 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -10,6 +10,7 @@ import { FeatureFlags, MlFeatureFlags, Gate, + GateType, } from '../snarky.js'; import { Field, Bool } from './core.js'; import { @@ -846,6 +847,18 @@ function dummyBase64Proof() { return withThreadPool(async () => Pickles.dummyBase64Proof()); } +// what feature flags to set to enable certain gate types + +const gateToFlag: Partial> = { + RangeCheck0: 'rangeCheck0', + RangeCheck1: 'rangeCheck1', + ForeignFieldAdd: 'foreignFieldAdd', + ForeignFieldMul: 'foreignFieldMul', + Xor16: 'xor', + Rot64: 'rot', + Lookup: 'lookup', +}; + function computeFeatureFlags(gates: Gate[]): MlFeatureFlags { let flags: FeatureFlags = { rangeCheck0: false, @@ -858,31 +871,8 @@ function computeFeatureFlags(gates: Gate[]): MlFeatureFlags { runtimeTables: false, }; for (let gate of gates) { - switch (gate.type) { - case 'RangeCheck0': - flags.rangeCheck0 = true; - break; - case 'RangeCheck1': - flags.rangeCheck1 = true; - break; - case 'ForeignFieldAdd': - flags.foreignFieldAdd = true; - break; - case 'ForeignFieldMul': - flags.foreignFieldMul = true; - break; - case 'Xor16': - flags.xor = true; - break; - case 'Rot64': - flags.rot = true; - break; - case 'Lookup': - flags.lookup = true; - break; - default: - break; - } + let flag = gateToFlag[gate.type]; + if (flag !== undefined) flags[flag] = true; } return [ 0, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 7a0f16f129..6145412344 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -12,7 +12,7 @@ import type { } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; -export { ProvablePure, Provable, Ledger, Pickles, Gate }; +export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType }; // internal export { From 6b776e2161dd88162304ff81d25624e86d21efb7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 13 Oct 2023 10:05:47 +0200 Subject: [PATCH 0197/1215] address feedback: add doccomment --- src/lib/gadgets/gadgets.ts | 31 ++++++++++++++++++++++++++++++- src/lib/gadgets/range-check.ts | 3 +-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 107ce568bf..b4c799b2cb 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -1,7 +1,36 @@ +/** + * Wrapper file for various gadgets, with a namespace and doccomments. + */ import { rangeCheck64 } from './range-check.js'; +import { Field } from '../core.js'; export { Gadgets }; const Gadgets = { - rangeCheck64, + /** + * Asserts that the input value is in the range [0, 2^64). + * + * This function proves that the provided field element can be represented with 64 bits. + * If the field element exceeds 64 bits, an error is thrown. + * + * @param x - The value to be range-checked. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * rangeCheck64(x); // successfully proves 64-bit range + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * rangeCheck64(xLarge); // throws an error since input exceeds 64 bits + * ``` + * + * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, + * and don't pass the 64-bit check. If you want to prove that a value lies in the int64 range [-2^63, 2^63), + * you could use `rangeCheck64(x.add(1n << 63n))`. + */ + rangeCheck64(x: Field) { + return rangeCheck64(x); + }, }; diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index d244b83ec4..d27d4807a4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -4,8 +4,7 @@ import * as Gates from '../gates.js'; export { rangeCheck64 }; /** - * Asserts that x is in the range [0, 2^64) - * @param x field element + * Asserts that x is in the range [0, 2^64), handles constant case */ function rangeCheck64(x: Field) { if (x.isConstant()) { From aa26e467b114bf1d78a2c5602cecffa82ac529ca Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 13 Oct 2023 10:30:01 +0200 Subject: [PATCH 0198/1215] fixup typo --- src/lib/zkapp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index fb03bcbd92..cf91b86108 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -674,7 +674,7 @@ class SmartContract { (instance as any)[methodName](publicInput, ...args); }; }); - // run methods once to get information tshat we need already at compile time + // run methods once to get information that we need already at compile time let methodsMeta = this.analyzeMethods(); let gates = methodIntfs.map((intf) => methodsMeta[intf.methodName].gates); let { From 68b9ba89d4daeeb59f5e93e65c557be3d6d96328 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 16 Oct 2023 18:14:25 +0200 Subject: [PATCH 0199/1215] address feedback: document new interfaces --- src/snarky.d.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 6145412344..f3e4d1e9e4 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -282,6 +282,14 @@ declare const Snarky: { }; gates: { + /** + * Range check gate + * + * @param v0 field var to be range checked + * @param v0p bits 16 to 88 as 6 12-bit limbs + * @param v0c bits 0 to 16 as 8 2-bit limbs + * @param compact boolean field elements -- whether to use "compact mode" + */ rangeCheck0( v0: FieldVar, v0p: [0, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar], @@ -564,16 +572,30 @@ type MlFeatureFlags = [ declare namespace Pickles { type Proof = unknown; // opaque to js type Statement = [_: 0, publicInput: MlArray, publicOutput: MlArray]; + + /** + * A "rule" is a circuit plus some metadata for `Pickles.compile` + */ type Rule = { identifier: string; + /** + * The main circuit functions + */ main: (publicInput: MlArray) => { publicOutput: MlArray; previousStatements: MlArray>; shouldVerify: MlArray; }; + /** + * Feature flags which enable certain custom gates + */ featureFlags: MlFeatureFlags; + /** + * Description of previous proofs to verify in this rule + */ proofsToVerify: MlArray<{ isSelf: true } | { isSelf: false; tag: unknown }>; }; + type Prover = ( publicInput: MlArray, previousProofs: MlArray From 5ef100f13ba69d5d1b815d435995fba29374fb1c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 16 Oct 2023 18:14:29 +0200 Subject: [PATCH 0200/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a6b9460a6f..851d3df238 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a6b9460a6f113bcf82912b6f75efbf9842a4b356 +Subproject commit 851d3df2385c693bd4f652ad7a0a1af98491e99c From f9a0935ddb7d68e7c9ab920a4a68ec6742971246 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 16 Oct 2023 18:26:39 +0200 Subject: [PATCH 0201/1215] simplify --- src/lib/gadgets/bitwise.ts | 91 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index c556e8deb3..17e747481f 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -37,29 +37,40 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); - // check that both elements fit into length bits as prover - fitsInBits(a, length); - fitsInBits(b, length); + // sanity check as prover to check that both elements fit into length bits + Provable.asProver(() => { + assert( + a.toBigInt() < 2 ** length, + `${a.toBigInt()} does not fit into ${length} bits` + ); + + assert( + b.toBigInt() < 2 ** length, + `${b.toBigInt()} does not fit into ${length} bits` + ); + }); // handle constant case if (a.isConstant() && b.isConstant()) { return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } + // calculate expect xor output let outputXor = Provable.witness( Field, () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) ); - // Obtain pad length until the length is a multiple of 4*n for n-bit length lookup table + // obtain pad length until the length is a multiple of 4*n for n-bit length lookup table let padLength = length; if (length % (4 * lengthXor) !== 0) { padLength = length + 4 * lengthXor - (length % (4 * lengthXor)); } - // recursively build xor gadget + // recursively build xor gadget chain buildXor(a, b, outputXor, padLength, lengthXor); + // return the result of the xor operation return outputXor; } @@ -67,18 +78,18 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { function buildXor( a: Field, b: Field, - outputXor: Field, + expectedOutput: Field, padLength: number, lengthXor: number ) { // if inputs are zero and length is zero, add the zero check if (padLength === 0) { - Gates.zeroCheck(a, b, outputXor); + Gates.zeroCheck(a, b, expectedOutput); let zero = new Field(0); zero.assertEquals(a); zero.assertEquals(b); - zero.assertEquals(outputXor); + zero.assertEquals(expectedOutput); } else { // nibble offsets let first = lengthXor; @@ -86,23 +97,28 @@ function buildXor( let third = second + lengthXor; let fourth = third + lengthXor; - let in1_0 = sliceBits(a, 0, first); - let in1_1 = sliceBits(a, first, second); - let in1_2 = sliceBits(a, second, third); - let in1_3 = sliceBits(a, third, fourth); - let in2_0 = sliceBits(b, 0, first); - let in2_1 = sliceBits(b, first, second); - let in2_2 = sliceBits(b, second, third); - let in2_3 = sliceBits(b, third, fourth); - let out_0 = sliceBits(outputXor, 0, first); - let out_1 = sliceBits(outputXor, first, second); - let out_2 = sliceBits(outputXor, second, third); - let out_3 = sliceBits(outputXor, third, fourth); + // slices of a + let in1_0 = witnessSlices(a, 0, first); + let in1_1 = witnessSlices(a, first, second); + let in1_2 = witnessSlices(a, second, third); + let in1_3 = witnessSlices(a, third, fourth); + + // slices of b + let in2_0 = witnessSlices(b, 0, first); + let in2_1 = witnessSlices(b, first, second); + let in2_2 = witnessSlices(b, second, third); + let in2_3 = witnessSlices(b, third, fourth); + + // slice of expected output + let out0 = witnessSlices(expectedOutput, 0, first); + let out1 = witnessSlices(expectedOutput, first, second); + let out2 = witnessSlices(expectedOutput, second, third); + let out3 = witnessSlices(expectedOutput, third, fourth); Gates.xor( a, b, - outputXor, + expectedOutput, in1_0, in1_1, in1_2, @@ -111,25 +127,25 @@ function buildXor( in2_1, in2_2, in2_3, - out_0, - out_1, - out_2, - out_3 + out0, + out1, + out2, + out3 ); let nextIn1 = witnessNextValue(a, in1_0, in1_1, in1_2, in1_3, lengthXor); let nextIn2 = witnessNextValue(b, in2_0, in2_1, in2_2, in2_3, lengthXor); - let nextOut = witnessNextValue( - outputXor, - out_0, - out_1, - out_2, - out_3, + let nextExpectedOutput = witnessNextValue( + expectedOutput, + out0, + out1, + out2, + out3, lengthXor ); let next_length = padLength - 4 * lengthXor; - buildXor(nextIn1, nextIn2, nextOut, next_length, lengthXor); + buildXor(nextIn1, nextIn2, nextExpectedOutput, next_length, lengthXor); } } @@ -139,16 +155,7 @@ function assert(stmt: boolean, message?: string) { } } -function fitsInBits(word: Field, length: number) { - Provable.asProver(() => { - assert( - word.toBigInt() < 2 ** length, - `${word.toBigInt()} does not fit into ${length} bits` - ); - }); -} - -function sliceBits(f: Field, start: number, stop = -1) { +function witnessSlices(f: Field, start: number, stop = -1) { if (stop !== -1 && stop <= start) throw Error('stop offset must be greater than start offset'); From ddb9200e0be35ba2903d01d588a32358b8bedb5b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 10:32:59 -0700 Subject: [PATCH 0202/1215] feat(gates.ts): add rot function --- src/lib/gates.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 503c4d2c6a..bb2d3b9750 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64 }; +export { rangeCheck64, rot }; /** * Asserts that x is at most 64 bits @@ -42,6 +42,27 @@ function rangeCheck64(x: Field) { ); } +function rot( + word: FieldVar, + rotated: FieldVar, + excess: FieldVar, + limbs: [0, FieldVar, FieldVar, FieldVar, FieldVar], + crumbs: [ + 0, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar, + FieldVar + ], + two_to_rot: FieldConst +) { + Snarky.gates.rot(word, rotated, excess, limbs, crumbs, two_to_rot); +} + function getBits(x: bigint, start: number, length: number) { return FieldConst.fromBigint( (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n) From 65d97bed322dac01be0eae6097840a9a51b1af36 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 10:35:31 -0700 Subject: [PATCH 0203/1215] feat(rot.ts): add new rotation function for bitwise operations This commit introduces a new function 'rot' in rot.ts file which performs bitwise rotation on a given word. It supports both left and right rotation modes. The function also includes a check for the number of bits to be rotated, ensuring it is within the range of 0 to 64. The rotation function is designed to work with both constant and provable words. --- src/lib/gadgets/rot.ts | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/lib/gadgets/rot.ts diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts new file mode 100644 index 0000000000..9cc37be4b2 --- /dev/null +++ b/src/lib/gadgets/rot.ts @@ -0,0 +1,44 @@ +import { Field } from '../field.js'; +import { Provable } from '../provable.js'; +import { Field as Fp } from '../../provable/field-bigint.js'; +import * as Gates from '../gates.js'; + +export { rot }; + +type RotationMode = 'left' | 'right'; + +function rot(word: Field, mode: RotationMode, bits: number) { + // Check that the rotation bits are in range + checkBits(bits); + + if (word.isConstant()) { + return rot_constant(word, mode, bits); + } else { + return rot_provable(word, mode, bits); + } +} + +function checkBits(bits: number) { + if (bits < 0 || bits > 64) { + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); + } +} + +function rot_constant(word: Field, mode: RotationMode, bits: number) { + let x = word.toBigInt(); + if (mode === 'left') { + return (x << BigInt(bits)) % (1n << 64n); + } else { + return (x >> BigInt(bits)) % (1n << 64n); + } +} + +function rot_provable(word: Field, mode: RotationMode, bits: number) { + // Check that the input word is at most 64 bits. + // Compute actual length depending on whether the rotation mode is "left" or "right" + // Auxiliary BigInt values + // Compute rotated word + // Compute current row + // Compute next row + // Compute following row +} From d92b6fa610e257f8d0d40d4732bd45362d8ba32e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 16 Oct 2023 21:00:20 +0200 Subject: [PATCH 0204/1215] revert excessive testing cost --- src/lib/gadgets/gadgets.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 6d65c57d50..13a44a059d 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -35,7 +35,7 @@ let maybeUint64: Spec = { // do a couple of proofs // TODO: we use this as a test because there's no way to check custom gates quickly :( -equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 20 })( +equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); return true; From e69a93fe84e4fa8a496410d899d26a1cc8754ede Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:09:46 -0700 Subject: [PATCH 0205/1215] chore(bindings): update bindings subproject to latest commit --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1f7443d077..13cb203feb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1f7443d0776382bf39bf13ea0becd59712abaf2f +Subproject commit 13cb203febb168a47c82fa83013e7e4b4193d438 From e040fe7d0f46ed4fb67a7b7851061935a4681619 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:14:37 -0700 Subject: [PATCH 0206/1215] refactor(snarky.d.ts): replace hardcoded arrays with MlArray for limbs and crumbs --- src/snarky.d.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 1b3cdec993..d5be6ac756 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -303,18 +303,8 @@ declare const Snarky: { word: FieldVar, rotated: FieldVar, excess: FieldVar, - limbs: [0, FieldVar, FieldVar, FieldVar, FieldVar], - crumbs: [ - 0, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar - ], + limbs: MlArray, + crumbs: MlArray, two_to_rot: FieldConst ): void; }; From 82dfa33cbf8fa1a097d9ce4f71dbc734553e0ee2 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:15:16 -0700 Subject: [PATCH 0207/1215] feat(gates.ts): add MlArray import to convert limbs and crumbs arrays to MlArray --- src/lib/gates.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index bb2d3b9750..175dc72afc 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,5 +1,6 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; +import { MlArray } from './ml/base.js'; export { rangeCheck64, rot }; @@ -43,24 +44,21 @@ function rangeCheck64(x: Field) { } function rot( - word: FieldVar, - rotated: FieldVar, - excess: FieldVar, - limbs: [0, FieldVar, FieldVar, FieldVar, FieldVar], - crumbs: [ - 0, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar - ], - two_to_rot: FieldConst + word: Field, + rotated: Field, + excess: Field, + limbs: [Field, Field, Field, Field], + crumbs: [Field, Field, Field, Field, Field, Field, Field, Field], + two_to_rot: Field ) { - Snarky.gates.rot(word, rotated, excess, limbs, crumbs, two_to_rot); + Snarky.gates.rot( + word.value, + rotated.value, + excess.value, + MlArray.to(limbs.map((x) => x.value)), + MlArray.to(crumbs.map((x) => x.value)), + FieldConst.fromBigint(two_to_rot.toBigInt()) + ); } function getBits(x: bigint, start: number, length: number) { From a63c0d317dfc32613e3b9c5fe570cc377dd36ac5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:16:05 -0700 Subject: [PATCH 0208/1215] feat(rot.ts): implement provable rotation function --- src/lib/gadgets/rot.ts | 138 +++++++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 9cc37be4b2..10a45fc81a 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -1,44 +1,142 @@ import { Field } from '../field.js'; +import { UInt64 } from '../int.js'; import { Provable } from '../provable.js'; import { Field as Fp } from '../../provable/field-bigint.js'; import * as Gates from '../gates.js'; export { rot }; -type RotationMode = 'left' | 'right'; +const MAX_BITS = 64 as const; -function rot(word: Field, mode: RotationMode, bits: number) { +function rot(word: Field, direction: 'left' | 'right' = 'left', bits: number) { // Check that the rotation bits are in range - checkBits(bits); - - if (word.isConstant()) { - return rot_constant(word, mode, bits); - } else { - return rot_provable(word, mode, bits); - } -} - -function checkBits(bits: number) { if (bits < 0 || bits > 64) { throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); } -} -function rot_constant(word: Field, mode: RotationMode, bits: number) { - let x = word.toBigInt(); - if (mode === 'left') { - return (x << BigInt(bits)) % (1n << 64n); + if (word.isConstant()) { + return new Field(Fp.rot64(word.toBigInt(), bits, direction === 'left')); } else { - return (x >> BigInt(bits)) % (1n << 64n); + // Check that the input word is at most 64 bits. + UInt64.check(UInt64.from(word)); + return rot_provable(word, direction, bits); } } -function rot_provable(word: Field, mode: RotationMode, bits: number) { +function rot_provable( + word: Field, + direction: 'left' | 'right' = 'left', + bits: number +) { // Check that the input word is at most 64 bits. + Provable.asProver(() => { + if (word.toBigInt() < 2 ** MAX_BITS) { + throw Error( + `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` + ); + } + }); + // Compute actual length depending on whether the rotation mode is "left" or "right" - // Auxiliary BigInt values + let rotationBits = bits; + if (direction === 'right') { + rotationBits = MAX_BITS - bits; + } + // Compute rotated word + const [rotated, excess, shifted, bound] = computeRotatedWord( + word, + rotationBits + ); + // Compute current row + Gates.rot( + word, + rotated, + excess, + [ + witnessSlices(bound, 52, 64), + witnessSlices(bound, 40, 52), + witnessSlices(bound, 28, 40), + witnessSlices(bound, 16, 28), + ], + [ + witnessSlices(bound, 14, 16), + witnessSlices(bound, 12, 14), + witnessSlices(bound, 10, 12), + witnessSlices(bound, 8, 10), + witnessSlices(bound, 6, 8), + witnessSlices(bound, 4, 6), + witnessSlices(bound, 2, 4), + witnessSlices(bound, 0, 2), + ], + Field.from(2n ** 64n) + ); // Compute next row + Gates.rangeCheck64(shifted); // Compute following row + Gates.rangeCheck64(excess); + return rotated; +} + +function computeRotatedWord(word: Field, rotationBits: number) { + // Auxiliary BigInt values + const big2Power64 = 2n ** 64n; + const big2PowerRot = 2n ** BigInt(rotationBits); + const wordBigInt = word.toBigInt(); + + return Provable.witness(Provable.Array(Field, 4), () => { + // Assert that the word is at most 64 bits. + if (wordBigInt < big2Power64) { + throw Error( + `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` + ); + } + + // Obtain rotated output, excess, and shifted for the equation + // word * 2^rot = excess * 2^64 + shifted + const { quotient: excessBigInt, remainder: shiftedBigInt } = divRem( + wordBigInt * big2PowerRot, + big2Power64 + ); + + // Compute rotated value as + // rotated = excess + shifted + const rotatedBig = shiftedBigInt + excessBigInt; + + // Compute bound that is the right input of FFAdd equation + const boundBig = excessBigInt + big2Power64 - big2PowerRot; + + // Convert back to field + const shifted = Field.from(shiftedBigInt); + const excess = Field.from(excessBigInt); + const rotated = Field.from(rotatedBig); + const bound = Field.from(boundBig); + + return [rotated, excess, shifted, bound]; + }); +} + +function witnessSlices(f: Field, start: number, stop = -1) { + if (stop !== -1 && stop <= start) { + throw Error('stop must be greater than start'); + } + + return Provable.witness(Field, () => { + let bits = f.toBits(); + if (stop > bits.length) { + throw Error('stop must be less than bit-length'); + } + if (stop === -1) { + stop = bits.length; + } + + return Field.fromBits(bits.slice(start, stop)); + }); +} + +function divRem(numerator: bigint, denominator: bigint) { + const quotient = numerator / denominator; + const remainder = numerator - denominator * quotient; + return { quotient, remainder }; } From 19bfedfbd097fb05bd744606c683a5200b59a891 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:27:30 -0700 Subject: [PATCH 0209/1215] refactor(rot.ts): simplify rot function by removing unnecessary checks and conditions --- src/lib/gadgets/rot.ts | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 10a45fc81a..6dd10f8adb 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -1,33 +1,12 @@ import { Field } from '../field.js'; -import { UInt64 } from '../int.js'; import { Provable } from '../provable.js'; -import { Field as Fp } from '../../provable/field-bigint.js'; import * as Gates from '../gates.js'; export { rot }; const MAX_BITS = 64 as const; -function rot(word: Field, direction: 'left' | 'right' = 'left', bits: number) { - // Check that the rotation bits are in range - if (bits < 0 || bits > 64) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); - } - - if (word.isConstant()) { - return new Field(Fp.rot64(word.toBigInt(), bits, direction === 'left')); - } else { - // Check that the input word is at most 64 bits. - UInt64.check(UInt64.from(word)); - return rot_provable(word, direction, bits); - } -} - -function rot_provable( - word: Field, - direction: 'left' | 'right' = 'left', - bits: number -) { +function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { // Check that the input word is at most 64 bits. Provable.asProver(() => { if (word.toBigInt() < 2 ** MAX_BITS) { From 1909678d2d6320d5b102aead523c48c35c5e03c8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:28:31 -0700 Subject: [PATCH 0210/1215] feat(field.ts): add rot64 method to Field class for bit rotation --- src/lib/field.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/lib/field.ts b/src/lib/field.ts index 29193d64db..5e0a8fd995 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -5,6 +5,7 @@ import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; +import { rot } from './gadgets/rot.js'; // external API export { Field }; @@ -589,6 +590,33 @@ class Field { return new Field(z); } + /** + * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. + * + * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. + * + * ```typescript + * let a = Field(12); + * let b = a.rot(2, 'left'); // left rotation by 2 bit + * c.assertEquals(20); + * ``` + * + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction (true) left or (false) right rotation direction. + */ + rot64(bits: number, direction: 'left' | 'right' = 'left') { + // Check that the rotation bits are in range + if (bits < 0 || bits > 64) { + throw Error(`rot64: expected bits to be between 0 and 64, got ${bits}`); + } + if (this.isConstant()) { + return new Field(Fp.rot64(this.toBigInt(), bits, direction === 'left')); + } else { + return rot(this, bits, direction); + } + } + /** * @deprecated use `x.equals(0)` which is equivalent */ From a94394f1dfcc66068ebbd58728d3c172e38609ac Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 12:36:50 -0700 Subject: [PATCH 0211/1215] feat(field.unit-test.ts, int.ts): add rotation test --- src/lib/field.unit-test.ts | 18 ++++++++++++++++++ src/lib/int.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index f28f688bb3..f4fe4e4c4a 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -35,6 +35,24 @@ test(Random.field, Random.json.field, (x, y, assert) => { assert(z.toJSON() === y); }); +// rotation +test( + Random.uint64, + Random.nat(64), + Random.boolean, + (x, n, direction, assert) => { + let z = Field(x); + let r1 = Fp.rot64(x, n, direction); + Provable.runAndCheck(() => { + let r2 = Provable.witness(Field, () => z).rot64( + n, + direction ? 'left' : 'right' + ); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + } +); + // handles small numbers test(Random.nat(1000), (n, assert) => { assert(Field(n).toString() === String(n)); diff --git a/src/lib/int.ts b/src/lib/int.ts index f4143a9a5b..5bef34beb2 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -371,6 +371,22 @@ class UInt64 extends CircuitValue { assertGreaterThanOrEqual(y: UInt64, message?: string) { y.assertLessThanOrEqual(this, message); } + + /** + * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * + * ```typescript + * let a = UInt64.from(12); + * let b = a.rot(2, true); // left rotation by 2 bit + * c.assertEquals(20); + * ``` + * + * @param bits amount of bits to rotate this {@link UInt64} element with. + * @param direction (true) left or (false) right rotation direction. + */ + rot(bits: number, direction: 'left' | 'right' = 'left') { + return new UInt64(this.value.rot64(bits, direction)); + } } /** * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. @@ -707,6 +723,22 @@ class UInt32 extends CircuitValue { assertGreaterThanOrEqual(y: UInt32, message?: string) { y.assertLessThanOrEqual(this, message); } + + /** + * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * + * ```typescript + * let a = UInt32.from(12); + * let b = a.rot(2, true); // left rotation by 2 bit + * c.assertEquals(20); + * ``` + * + * @param bits amount of bits to rotate this {@link UInt64} element with. + * @param direction (true) left or (false) right rotation direction. + */ + rot(bits: number, direction: 'left' | 'right' = 'left') { + return new UInt32(this.value.rot64(bits, direction)); + } } class Sign extends CircuitValue { From eed078cf3a528613663c70ea82a3903e17a9aeef Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 13:13:32 -0700 Subject: [PATCH 0212/1215] fix(rot.ts): correct condition to check if word is more than 64 bits --- src/lib/field.ts | 4 ---- src/lib/gadgets/rot.ts | 20 +++++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 5e0a8fd995..c919432261 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -606,10 +606,6 @@ class Field { * @param direction (true) left or (false) right rotation direction. */ rot64(bits: number, direction: 'left' | 'right' = 'left') { - // Check that the rotation bits are in range - if (bits < 0 || bits > 64) { - throw Error(`rot64: expected bits to be between 0 and 64, got ${bits}`); - } if (this.isConstant()) { return new Field(Fp.rot64(this.toBigInt(), bits, direction === 'left')); } else { diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 6dd10f8adb..64d2963c50 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -7,9 +7,14 @@ export { rot }; const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { + // Check that the rotation bits are in range + if (bits < 0 || bits > 64) { + throw Error(`rot64: expected bits to be between 0 and 64, got ${bits}`); + } + // Check that the input word is at most 64 bits. Provable.asProver(() => { - if (word.toBigInt() < 2 ** MAX_BITS) { + if (word.toBigInt() > 2 ** MAX_BITS) { throw Error( `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` ); @@ -59,14 +64,15 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { } function computeRotatedWord(word: Field, rotationBits: number) { - // Auxiliary BigInt values - const big2Power64 = 2n ** 64n; - const big2PowerRot = 2n ** BigInt(rotationBits); - const wordBigInt = word.toBigInt(); - return Provable.witness(Provable.Array(Field, 4), () => { + const wordBigInt = word.toBigInt(); + + // Auxiliary BigInt values + const big2Power64 = 2n ** 64n; + const big2PowerRot = 2n ** BigInt(rotationBits); + // Assert that the word is at most 64 bits. - if (wordBigInt < big2Power64) { + if (wordBigInt > big2Power64) { throw Error( `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` ); From 60517f3316ce5f6ae7d41affc20a502bc7832178 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 13:19:46 -0700 Subject: [PATCH 0213/1215] docs(CHANGELOG.md): add entry for bitwise ROT operation support for native field elements --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb508bf0b..466bc5df9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 +- Added bitwise `ROT` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1182 + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes From b8cc337dab014b4beb24673cb792a99b3f1ccec6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 15:46:51 -0700 Subject: [PATCH 0214/1215] refactor(rot.ts): replace hardcoded value 64 with constant MAX_BITS --- src/lib/gadgets/rot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 64d2963c50..5ab1169282 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -8,7 +8,7 @@ const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { // Check that the rotation bits are in range - if (bits < 0 || bits > 64) { + if (bits < 0 || bits > MAX_BITS) { throw Error(`rot64: expected bits to be between 0 and 64, got ${bits}`); } From 3c8dd487d2c3835e7497c4009ebbc728b0011d80 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 15:47:24 -0700 Subject: [PATCH 0215/1215] refactor(rot.ts): extract rotation logic into separate rotate function --- src/lib/gadgets/rot.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 5ab1169282..6358420700 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -2,7 +2,7 @@ import { Field } from '../field.js'; import { Provable } from '../provable.js'; import * as Gates from '../gates.js'; -export { rot }; +export { rot, rotate }; const MAX_BITS = 64 as const; @@ -21,17 +21,8 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { } }); - // Compute actual length depending on whether the rotation mode is "left" or "right" - let rotationBits = bits; - if (direction === 'right') { - rotationBits = MAX_BITS - bits; - } - // Compute rotated word - const [rotated, excess, shifted, bound] = computeRotatedWord( - word, - rotationBits - ); + const [rotated, excess, shifted, bound] = rotate(word, bits, direction); // Compute current row Gates.rot( @@ -63,7 +54,17 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { return rotated; } -function computeRotatedWord(word: Field, rotationBits: number) { +function rotate( + word: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { + // Compute actual length depending on whether the rotation mode is "left" or "right" + let rotationBits = bits; + if (direction === 'right') { + rotationBits = MAX_BITS - bits; + } + return Provable.witness(Provable.Array(Field, 4), () => { const wordBigInt = word.toBigInt(); From eba9209e851cd8f18a67ce88c3716e179cdd6d6a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 15:48:06 -0700 Subject: [PATCH 0216/1215] fix(rot.ts): correct error message to match function name 'rot' --- src/lib/gadgets/rot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 6358420700..193985cbbe 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -9,7 +9,7 @@ const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot64: expected bits to be between 0 and 64, got ${bits}`); + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); } // Check that the input word is at most 64 bits. From 8945d7f1d2093d189a7d20196701dd584dffef09 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 15:56:20 -0700 Subject: [PATCH 0217/1215] refactor(rot.ts): simplify computation and conversion of rotated, excess, shifted, bound values for readability --- src/lib/gadgets/rot.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 193985cbbe..9c54b7ffa2 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -67,7 +67,6 @@ function rotate( return Provable.witness(Provable.Array(Field, 4), () => { const wordBigInt = word.toBigInt(); - // Auxiliary BigInt values const big2Power64 = 2n ** 64n; const big2PowerRot = 2n ** BigInt(rotationBits); @@ -81,25 +80,17 @@ function rotate( // Obtain rotated output, excess, and shifted for the equation // word * 2^rot = excess * 2^64 + shifted - const { quotient: excessBigInt, remainder: shiftedBigInt } = divRem( + const { quotient: excess, remainder: shifted } = divideWithRemainder( wordBigInt * big2PowerRot, big2Power64 ); - // Compute rotated value as // rotated = excess + shifted - const rotatedBig = shiftedBigInt + excessBigInt; - + const rotated = shifted + excess; // Compute bound that is the right input of FFAdd equation - const boundBig = excessBigInt + big2Power64 - big2PowerRot; - - // Convert back to field - const shifted = Field.from(shiftedBigInt); - const excess = Field.from(excessBigInt); - const rotated = Field.from(rotatedBig); - const bound = Field.from(boundBig); + const bound = excess + big2Power64 - big2PowerRot; - return [rotated, excess, shifted, bound]; + return [rotated, excess, shifted, bound].map(Field.from); }); } @@ -121,7 +112,7 @@ function witnessSlices(f: Field, start: number, stop = -1) { }); } -function divRem(numerator: bigint, denominator: bigint) { +function divideWithRemainder(numerator: bigint, denominator: bigint) { const quotient = numerator / denominator; const remainder = numerator - denominator * quotient; return { quotient, remainder }; From 6745559171b92615ae12a90a5b08f1c751861303 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 16:10:19 -0700 Subject: [PATCH 0218/1215] refactor: rename rot64 method to rot in field.ts, field.unit-test.ts, int.ts --- src/lib/field.ts | 4 ++-- src/lib/field.unit-test.ts | 4 ++-- src/lib/int.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index c919432261..db7ed19e61 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -605,9 +605,9 @@ class Field { * @param bits amount of bits to rotate this {@link Field} element with. * @param direction (true) left or (false) right rotation direction. */ - rot64(bits: number, direction: 'left' | 'right' = 'left') { + rot(bits: number, direction: 'left' | 'right' = 'left') { if (this.isConstant()) { - return new Field(Fp.rot64(this.toBigInt(), bits, direction === 'left')); + return new Field(Fp.rot(this.toBigInt(), bits, direction === 'left')); } else { return rot(this, bits, direction); } diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index f4fe4e4c4a..7f23e76d7a 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -42,9 +42,9 @@ test( Random.boolean, (x, n, direction, assert) => { let z = Field(x); - let r1 = Fp.rot64(x, n, direction); + let r1 = Fp.rot(x, n, direction); Provable.runAndCheck(() => { - let r2 = Provable.witness(Field, () => z).rot64( + let r2 = Provable.witness(Field, () => z).rot( n, direction ? 'left' : 'right' ); diff --git a/src/lib/int.ts b/src/lib/int.ts index 5bef34beb2..3fd35c3006 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -385,7 +385,7 @@ class UInt64 extends CircuitValue { * @param direction (true) left or (false) right rotation direction. */ rot(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt64(this.value.rot64(bits, direction)); + return new UInt64(this.value.rot(bits, direction)); } } /** @@ -737,7 +737,7 @@ class UInt32 extends CircuitValue { * @param direction (true) left or (false) right rotation direction. */ rot(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt32(this.value.rot64(bits, direction)); + return new UInt32(this.value.rot(bits, direction)); } } From e0bcb0f6a43703e860500101967a419f80728d53 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 16:14:12 -0700 Subject: [PATCH 0219/1215] feat(field.ts, int.ts): add default values for bits parameter in rot method to improve usability This change allows users to call the rot method without specifying the bits parameter, which will default to 64 for Field class, UInt64.NUM_BITS for UInt64 class, and UInt32.NUM_BITS for UInt32 class. --- src/lib/field.ts | 2 +- src/lib/int.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index db7ed19e61..00a6aaf0d1 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -605,7 +605,7 @@ class Field { * @param bits amount of bits to rotate this {@link Field} element with. * @param direction (true) left or (false) right rotation direction. */ - rot(bits: number, direction: 'left' | 'right' = 'left') { + rot(bits: number = 64, direction: 'left' | 'right' = 'left') { if (this.isConstant()) { return new Field(Fp.rot(this.toBigInt(), bits, direction === 'left')); } else { diff --git a/src/lib/int.ts b/src/lib/int.ts index 3fd35c3006..ea84a413ac 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -384,7 +384,7 @@ class UInt64 extends CircuitValue { * @param bits amount of bits to rotate this {@link UInt64} element with. * @param direction (true) left or (false) right rotation direction. */ - rot(bits: number, direction: 'left' | 'right' = 'left') { + rot(bits: number = UInt64.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt64(this.value.rot(bits, direction)); } } @@ -736,7 +736,7 @@ class UInt32 extends CircuitValue { * @param bits amount of bits to rotate this {@link UInt64} element with. * @param direction (true) left or (false) right rotation direction. */ - rot(bits: number, direction: 'left' | 'right' = 'left') { + rot(bits: number = UInt32.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt32(this.value.rot(bits, direction)); } } From e8e0c51c25a189865b135688f9c1bc61fc5bf279 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 16:21:46 -0700 Subject: [PATCH 0220/1215] fix(field.ts, int.ts): correct the documentation for the rot function - Update the example output to match the correct result of the operation - Change the direction parameter description from boolean to 'left' or 'right' string values for better readability and understanding of the function usage --- src/lib/field.ts | 4 ++-- src/lib/int.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 00a6aaf0d1..6471d02456 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -599,11 +599,11 @@ class Field { * ```typescript * let a = Field(12); * let b = a.rot(2, 'left'); // left rotation by 2 bit - * c.assertEquals(20); + * b.assertEquals(48); * ``` * * @param bits amount of bits to rotate this {@link Field} element with. - * @param direction (true) left or (false) right rotation direction. + * @param direction left or right rotation direction. */ rot(bits: number = 64, direction: 'left' | 'right' = 'left') { if (this.isConstant()) { diff --git a/src/lib/int.ts b/src/lib/int.ts index ea84a413ac..6ea5f4dd75 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -377,12 +377,12 @@ class UInt64 extends CircuitValue { * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * * ```typescript * let a = UInt64.from(12); - * let b = a.rot(2, true); // left rotation by 2 bit - * c.assertEquals(20); + * let b = a.rot(2, 'left'); // left rotation by 2 bit + * b.assertEquals(48); * ``` * * @param bits amount of bits to rotate this {@link UInt64} element with. - * @param direction (true) left or (false) right rotation direction. + * @param direction left or right rotation direction. */ rot(bits: number = UInt64.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt64(this.value.rot(bits, direction)); @@ -729,12 +729,12 @@ class UInt32 extends CircuitValue { * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * * ```typescript * let a = UInt32.from(12); - * let b = a.rot(2, true); // left rotation by 2 bit - * c.assertEquals(20); + * let b = a.rot(2, 'left'); // left rotation by 2 bit + * b.assertEquals(48); * ``` * * @param bits amount of bits to rotate this {@link UInt64} element with. - * @param direction (true) left or (false) right rotation direction. + * @param direction left or right rotation direction. */ rot(bits: number = UInt32.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt32(this.value.rot(bits, direction)); From 6f3a6c568c3f8706ba0369e8f19a52ed73acae67 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 16:30:05 -0700 Subject: [PATCH 0221/1215] feat(gadgets.ts): add example e2e test --- src/examples/gadgets.ts | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/examples/gadgets.ts diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts new file mode 100644 index 0000000000..1bddf250c3 --- /dev/null +++ b/src/examples/gadgets.ts @@ -0,0 +1,54 @@ +import { Field, Provable, Experimental } from 'o1js'; + +Provable.runAndCheck(() => { + let f = Field(12); + let res = f.rot(2, 'left'); + Provable.log(res); + res.assertEquals(Field(48)); +}); + +let cs = Provable.constraintSystem(() => { + let res1 = Provable.witness(Field, () => Field(12).rot(2, 'left')); + let res2 = Provable.witness(Field, () => Field(12).rot(2, 'right')); + + res1.assertEquals(Field(48)); + res2.assertEquals(Field(3)); + + Provable.log(res1); + Provable.log(res2); +}); +console.log(cs); + +const ROT = Experimental.ZkProgram({ + methods: { + baseCase: { + privateInputs: [], + method: () => { + let a = Provable.witness(Field, () => Field(48)); + let actualLeft = a.rot(2, 'left'); + let actualRight = a.rot(2, 'right'); + + let expectedLeft = Field(192); + actualLeft.assertEquals(expectedLeft); + + let expectedRight = 12; + actualRight.assertEquals(expectedRight); + }, + }, + }, +}); + +console.log('compiling..'); + +console.time('compile'); +await ROT.compile(); +console.timeEnd('compile'); + +console.log('proving..'); + +console.time('prove'); +let proof = await ROT.baseCase(); +console.timeEnd('prove'); + +if (!(await ROT.verify(proof))) throw Error('Invalid proof'); +else console.log('proof valid'); From cd024e55abc97ca648b84ba1b2d9fefe9e2351a1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 17:52:31 -0700 Subject: [PATCH 0222/1215] refactor(rot.ts): split rot function into rot and rotate --- src/lib/gadgets/rot.ts | 86 +++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 9c54b7ffa2..d50ea03e1e 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -7,11 +7,26 @@ export { rot, rotate }; const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { + const [rotated, ,] = rotate(word, bits, direction); + return rotated; +} + +function rotate( + word: Field, + bits: number, + direction: 'left' | 'right' = 'left' +): [Field, Field, Field] { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); } + if (direction !== 'left' && direction !== 'right') { + throw Error( + `rot: expected direction to be 'left' or 'right', got ${direction}` + ); + } + // Check that the input word is at most 64 bits. Provable.asProver(() => { if (word.toBigInt() > 2 ** MAX_BITS) { @@ -21,8 +36,31 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { } }); - // Compute rotated word - const [rotated, excess, shifted, bound] = rotate(word, bits, direction); + const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; + const big2Power64 = 2n ** 64n; + const big2PowerRot = 2n ** BigInt(rotationBits); + + const [rotated, excess, shifted, bound] = Provable.witness( + Provable.Array(Field, 4), + () => { + const wordBigInt = word.toBigInt(); + + // Obtain rotated output, excess, and shifted for the equation + // word * 2^rot = excess * 2^64 + shifted + const { quotient: excess, remainder: shifted } = divideWithRemainder( + wordBigInt * big2PowerRot, + big2Power64 + ); + + // Compute rotated value as + // rotated = excess + shifted + const rotated = shifted + excess; + // Compute bound that is the right input of FFAdd equation + const bound = excess + big2Power64 - big2PowerRot; + + return [rotated, excess, shifted, bound].map(Field.from); + } + ); // Compute current row Gates.rot( @@ -45,53 +83,13 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { witnessSlices(bound, 2, 4), witnessSlices(bound, 0, 2), ], - Field.from(2n ** 64n) + Field.from(big2PowerRot) ); // Compute next row Gates.rangeCheck64(shifted); // Compute following row Gates.rangeCheck64(excess); - return rotated; -} - -function rotate( - word: Field, - bits: number, - direction: 'left' | 'right' = 'left' -) { - // Compute actual length depending on whether the rotation mode is "left" or "right" - let rotationBits = bits; - if (direction === 'right') { - rotationBits = MAX_BITS - bits; - } - - return Provable.witness(Provable.Array(Field, 4), () => { - const wordBigInt = word.toBigInt(); - // Auxiliary BigInt values - const big2Power64 = 2n ** 64n; - const big2PowerRot = 2n ** BigInt(rotationBits); - - // Assert that the word is at most 64 bits. - if (wordBigInt > big2Power64) { - throw Error( - `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` - ); - } - - // Obtain rotated output, excess, and shifted for the equation - // word * 2^rot = excess * 2^64 + shifted - const { quotient: excess, remainder: shifted } = divideWithRemainder( - wordBigInt * big2PowerRot, - big2Power64 - ); - // Compute rotated value as - // rotated = excess + shifted - const rotated = shifted + excess; - // Compute bound that is the right input of FFAdd equation - const bound = excess + big2Power64 - big2PowerRot; - - return [rotated, excess, shifted, bound].map(Field.from); - }); + return [rotated, excess, shifted]; } function witnessSlices(f: Field, start: number, stop = -1) { From 704c2d900bceb573fdab1102ef67ddb7bb54801f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 18:04:17 -0700 Subject: [PATCH 0223/1215] feat(gadgets.ts): add more test cases --- src/examples/gadgets.ts | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 1bddf250c3..1ff2c67ebd 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,11 +1,34 @@ import { Field, Provable, Experimental } from 'o1js'; -Provable.runAndCheck(() => { - let f = Field(12); - let res = f.rot(2, 'left'); - Provable.log(res); - res.assertEquals(Field(48)); -}); +function testRot( + word: Field, + bits: number, + mode: 'left' | 'right', + result: Field +) { + Provable.runAndCheck(() => { + let w = Provable.witness(Field, () => word); + let r = Provable.witness(Field, () => result); + let output = w.rot(bits, mode); + output.assertEquals(r); + }); +} + +console.log('Running positive tests...'); +testRot(Field(0), 0, 'left', Field(0)); +testRot(Field(0), 32, 'right', Field(0)); +testRot(Field(1), 1, 'left', Field(2)); +testRot(Field(1), 63, 'left', Field(9223372036854775808)); +testRot(Field(256), 4, 'right', Field(16)); + +// TODO: fix this test +// testRot(Field(6510615555426900570), 4, 'left', Field(11936128518282651045)); +//testRot(Field(6510615555426900570), 4, 'right', Field(11936128518282651045)); + +testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); +testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); +testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); +console.log('🎉 Passed positive tests'); let cs = Provable.constraintSystem(() => { let res1 = Provable.witness(Field, () => Field(12).rot(2, 'left')); @@ -17,7 +40,7 @@ let cs = Provable.constraintSystem(() => { Provable.log(res1); Provable.log(res2); }); -console.log(cs); +console.log('constraint system: ', cs); const ROT = Experimental.ZkProgram({ methods: { From fb347bd23726cff2d1a4ae314ffc415073c144fc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 18:05:55 -0700 Subject: [PATCH 0224/1215] chore(rot.ts): minor refactors --- src/lib/gadgets/rot.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index d50ea03e1e..1a99afcc8e 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -7,7 +7,7 @@ export { rot, rotate }; const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { - const [rotated, ,] = rotate(word, bits, direction); + const [rotated] = rotate(word, bits, direction); return rotated; } @@ -45,19 +45,18 @@ function rotate( () => { const wordBigInt = word.toBigInt(); - // Obtain rotated output, excess, and shifted for the equation + // Obtain rotated output, excess, and shifted for the equation: // word * 2^rot = excess * 2^64 + shifted const { quotient: excess, remainder: shifted } = divideWithRemainder( wordBigInt * big2PowerRot, big2Power64 ); - // Compute rotated value as + // Compute rotated value as: // rotated = excess + shifted const rotated = shifted + excess; // Compute bound that is the right input of FFAdd equation const bound = excess + big2Power64 - big2PowerRot; - return [rotated, excess, shifted, bound].map(Field.from); } ); From 98bd547be9c4af5979cf092954e4d0a56e6635dc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 16 Oct 2023 18:19:17 -0700 Subject: [PATCH 0225/1215] feat(gadgets.ts): enhance testRot function to log output for better debugging --- src/examples/gadgets.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 1ff2c67ebd..cba262e689 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -10,7 +10,10 @@ function testRot( let w = Provable.witness(Field, () => word); let r = Provable.witness(Field, () => result); let output = w.rot(bits, mode); - output.assertEquals(r); + Provable.asProver(() => { + Provable.log(`rot(${word}, ${bits}, ${mode}) = ${output}`); + }); + output.assertEquals(r, `rot(${word}, ${bits}, ${mode})`); }); } @@ -22,7 +25,8 @@ testRot(Field(1), 63, 'left', Field(9223372036854775808)); testRot(Field(256), 4, 'right', Field(16)); // TODO: fix this test -// testRot(Field(6510615555426900570), 4, 'left', Field(11936128518282651045)); +// 0x5A5A5A5A5A5A5A5A is 0xA5A5A5A5A5A5A5A5 both when rotate 4 bits left or right +// testRot(Field(6510615555426900570), 4, 'right', Field(11936128518282651045)); //testRot(Field(6510615555426900570), 4, 'right', Field(11936128518282651045)); testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); From 33db00dd543328cda94bbf130a2522cb877fe1ae Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 11:22:32 +0200 Subject: [PATCH 0226/1215] add tests --- src/examples/gadgets.ts | 10 ++--- src/index.ts | 4 +- src/lib/field.ts | 2 +- src/lib/gadgets/bitwise.ts | 1 + src/lib/gadgets/bitwise.unit-test.ts | 38 +++++++++++++++++++ src/lib/gadgets/gadgets.ts | 24 ++++++++++++ ....unit-test.ts => range-check.unit-test.ts} | 0 7 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 src/lib/gadgets/bitwise.unit-test.ts rename src/lib/gadgets/{gadgets.unit-test.ts => range-check.unit-test.ts} (100%) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 18e8fb28f2..8bcf698791 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,7 +1,7 @@ -import { Field, Provable, xor, Experimental } from 'o1js'; +import { Field, Provable, Gadgets, Experimental } from 'o1js'; Provable.runAndCheck(() => { - let res = xor( + let res = Gadgets.xor( Field(5215), Provable.witness(Field, () => Field(7812)), 16 @@ -9,11 +9,11 @@ Provable.runAndCheck(() => { Provable.log(res); }); -let res = xor(Field(2), Field(5), 4); +let res = Gadgets.xor(Field(2), Field(5), 4); Provable.log(res); let cs = Provable.constraintSystem(() => { - let res = xor( + let res = Gadgets.xor( Provable.witness(Field, () => Field(5215)), Provable.witness(Field, () => Field(7812)), 2 @@ -29,7 +29,7 @@ const XOR = Experimental.ZkProgram({ method: () => { let a = Provable.witness(Field, () => Field(5)); let b = Provable.witness(Field, () => Field(2)); - let actual = xor(a, b, 4); + let actual = Gadgets.xor(a, b, 4); let expected = Field(7); actual.assertEquals(expected); }, diff --git a/src/index.ts b/src/index.ts index 190aa61135..fcf49a582b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ -export { xor } from './lib/gadgets/bitwise.js'; - export type { ProvablePure } from './snarky.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; @@ -68,7 +66,7 @@ export { setGraphqlEndpoints, setArchiveGraphqlEndpoint, sendZkapp, - Lightnet + Lightnet, } from './lib/fetch.js'; export * as Encryption from './lib/encryption.js'; export * as Encoding from './bindings/lib/encoding.js'; diff --git a/src/lib/field.ts b/src/lib/field.ts index f77a57ffdf..24d44a37ce 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -5,7 +5,7 @@ import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; -import * as Gadgets from './gadgets/bitwise.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { Field }; diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 17e747481f..a5873a997d 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -115,6 +115,7 @@ function buildXor( let out2 = witnessSlices(expectedOutput, second, third); let out3 = witnessSlices(expectedOutput, third, fourth); + // assert that the xor of the slices is correct, 16 bit at a time Gates.xor( a, b, diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts new file mode 100644 index 0000000000..5e3caed13a --- /dev/null +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -0,0 +1,38 @@ +import { Fp, mod } from '../../bindings/crypto/finite_field.js'; +import { Field } from '../field.js'; +import { ZkProgram } from '../proof_system.js'; +import { Spec, equivalentAsync, field } from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { Gadgets } from './gadgets.js'; + +let Bitwise = ZkProgram({ + publicOutput: Field, + methods: { + xor: { + privateInputs: [Field, Field], + method(a: Field, b: Field) { + return Gadgets.xor(a, b, 64); + }, + }, + }, +}); + +await Bitwise.compile(); + +let maybeUint64: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + +// do a couple of proofs +equivalentAsync({ from: [maybeUint64, maybeUint64], to: field }, { runs: 3 })( + (x, y) => { + return Fp.xor(x, y); + }, + async (x, y) => { + let proof = await Bitwise.xor(x, y); + return proof.publicOutput; + } +); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b4c799b2cb..528da1f64e 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,6 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; +import { xor } from './bitwise.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -33,4 +34,27 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, + + /** + * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. + * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. + * + * ```typescript + * let a = Field(5); // ... 000101 + * let b = Field(3); // ... 000011 + * + * let c = xor(a, b, 2); // ... 000110 + * c.assertEquals(6); + * ``` + */ + xor(a: Field, b: Field, length: number, lengthXor = 4) { + return xor(a, b, length, lengthXor); + }, }; diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts similarity index 100% rename from src/lib/gadgets/gadgets.unit-test.ts rename to src/lib/gadgets/range-check.unit-test.ts From bce7ffe1f6c3b22f89d751d0fdd675d2619057b2 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 11:53:54 +0200 Subject: [PATCH 0227/1215] dump vks --- src/bindings | 2 +- src/examples/primitive_constraint_system.ts | 14 +++++++++++++- src/examples/regression_test.json | 13 +++++++++++++ src/examples/vk_regression.ts | 3 ++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/bindings b/src/bindings index 4aa3fc8835..6972f73332 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4aa3fc883539ea41aca0d37d6c8b86aacbd1e8d5 +Subproject commit 6972f7333206f17882330c74bd2b6e42bf295d01 diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 17735faa04..1ef5a5f87c 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -1,4 +1,4 @@ -import { Field, Group, Poseidon, Provable, Scalar } from 'o1js'; +import { Field, Group, Gadgets, Provable, Scalar } from 'o1js'; function mock(obj: { [K: string]: (...args: any) => void }, name: string) { let methodKeys = Object.keys(obj); @@ -63,4 +63,16 @@ const GroupMock = { }, }; +const BitwiseMock = { + xor() { + let a = Provable.witness(Field, () => new Field(5n)); + let b = Provable.witness(Field, () => new Field(5n)); + Gadgets.xor(a, b, 16); + Gadgets.xor(a, b, 32); + Gadgets.xor(a, b, 48); + Gadgets.xor(a, b, 64); + }, +}; + export const GroupCS = mock(GroupMock, 'Group Primitive'); +export const BitwiseCS = mock(BitwiseMock, 'Bitwise Primitive'); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index e492f2d163..817796fa3a 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -164,5 +164,18 @@ "data": "", "hash": "" } + }, + "Bitwise Primitive": { + "digest": "Bitwise Primitive", + "methods": { + "xor": { + "rows": 15, + "digest": "b3595a9cc9562d4f4a3a397b6de44971" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } } } \ No newline at end of file diff --git a/src/examples/vk_regression.ts b/src/examples/vk_regression.ts index 6dbcd31373..a76b41aef4 100644 --- a/src/examples/vk_regression.ts +++ b/src/examples/vk_regression.ts @@ -3,7 +3,7 @@ import { Voting_ } from './zkapps/voting/voting.js'; import { Membership_ } from './zkapps/voting/membership.js'; import { HelloWorld } from './zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from './zkapps/dex/dex.js'; -import { GroupCS } from './primitive_constraint_system.js'; +import { GroupCS, BitwiseCS } from './primitive_constraint_system.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; @@ -37,6 +37,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ TokenContract, createDex().Dex, GroupCS, + BitwiseCS, ]; let filePath = jsonPath ? jsonPath : './src/examples/regression_test.json'; From 70ba811168c00901c38c1c10db0456b25110f07d Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 11:54:30 +0200 Subject: [PATCH 0228/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 6972f73332..f8185abae7 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 6972f7333206f17882330c74bd2b6e42bf295d01 +Subproject commit f8185abae79db6dcb01212e57c4871052c09a1be From 458bd4646f9f1174400e2971834704b0435ef341 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 11:58:12 +0200 Subject: [PATCH 0229/1215] add doc commnets to gate --- src/lib/gates.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 4bcedf1ec5..354df690b7 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -43,7 +43,7 @@ function rangeCheck64(x: Field) { } /** - * + * Asserts that 16 bit limbs of input two elements are the correct XOR output */ function xor( input1: Field, @@ -57,10 +57,10 @@ function xor( in2_1: Field, in2_2: Field, in2_3: Field, - out_0: Field, - out_1: Field, - out_2: Field, - out_3: Field + out0: Field, + out1: Field, + out2: Field, + out3: Field ) { Snarky.gates.xor( input1.value, @@ -74,10 +74,10 @@ function xor( in2_1.value, in2_2.value, in2_3.value, - out_0.value, - out_1.value, - out_2.value, - out_3.value + out0.value, + out1.value, + out2.value, + out3.value ); } From 5b87fb7fec0a9628ca8298aec433725645a33e56 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:11:12 +0200 Subject: [PATCH 0230/1215] remove circular dependency --- src/examples/gadgets.ts | 6 +++--- src/lib/field.ts | 23 --------------------- src/lib/field.unit-test.ts | 6 +++++- src/lib/gadgets/bitwise.ts | 4 +++- src/lib/gadgets/bitwise.unit-test.ts | 4 ++-- src/lib/int.ts | 30 ---------------------------- 6 files changed, 13 insertions(+), 60 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 8bcf698791..608cdee2f7 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -2,9 +2,9 @@ import { Field, Provable, Gadgets, Experimental } from 'o1js'; Provable.runAndCheck(() => { let res = Gadgets.xor( - Field(5215), - Provable.witness(Field, () => Field(7812)), - 16 + Field(521515), + Provable.witness(Field, () => Field(771812)), + 32 ); Provable.log(res); }); diff --git a/src/lib/field.ts b/src/lib/field.ts index 24d44a37ce..6b9ff9082e 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -591,29 +591,6 @@ class Field { return new Field(z); } - /** - * Bitwise XOR gate on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). - * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. - * - * The `length` parameter lets you define how many bits should be compared. By default it is set to `32`. The output is not constrained to the length. - * - * **Note:** Specifying a larger `length` parameter adds additional constraints. - * - * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. - * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. - * - * ```typescript - * let a = Field(5); // ... 000101 - * let b = Field(3); // ... 000011 - * - * let c = a.xor(b); // ... 000110 - * c.assertEquals(6); - * ``` - */ - xor(y: Field | bigint | number | string, length: number = 32) { - return Gadgets.xor(this, Field.from(y), length); - } - /** * @deprecated use `x.equals(0)` which is equivalent */ diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 36b401e228..9ae4ee945a 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -17,6 +17,7 @@ import { bool, Spec, } from './testing/equivalent.js'; +import { Gadgets } from './gadgets/gadgets.js'; // types Field satisfies Provable; @@ -76,11 +77,14 @@ test(Random.field, Random.int(-5, 5), (x, k) => { let r1 = Fp.xor(BigInt(x), BigInt(y)); Provable.runAndCheck(() => { - let r2 = Provable.witness(Field, () => z).xor(y, length); + let zz = Provable.witness(Field, () => z); + let yy = Provable.witness(Field, () => Field(y)); + let r2 = Gadgets.xor(zz, yy, length); Provable.asProver(() => assert(r1 === r2.toBigInt())); }); }); }); + // Field | bigint parameter let fieldOrBigint = oneOf(field, bigintField); diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index a5873a997d..5149c52913 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,6 +1,6 @@ -import { Field, toFp } from '../field.js'; import { Provable } from '../provable.js'; import { Field as Fp } from '../../provable/field-bigint.js'; +import { Field } from '../field.js'; import * as Gates from '../gates.js'; export { xor }; @@ -9,6 +9,8 @@ export { xor }; * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. * + * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. + * * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. * * **Note:** Specifying a larger `length` parameter adds additional constraints. diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 5e3caed13a..d18d4bbe5d 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -1,8 +1,8 @@ -import { Fp, mod } from '../../bindings/crypto/finite_field.js'; -import { Field } from '../field.js'; import { ZkProgram } from '../proof_system.js'; import { Spec, equivalentAsync, field } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; +import { Fp, mod } from '../../bindings/crypto/finite_field.js'; +import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; let Bitwise = ZkProgram({ diff --git a/src/lib/int.ts b/src/lib/int.ts index bdf4a695cf..a48118fa2c 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -115,21 +115,6 @@ class UInt64 extends CircuitValue { return new UInt64(Field((1n << 64n) - 1n)); } - /** - * Bitwise XOR gate on {@link UInt64} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). - * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. It applies XOR to all 64 bits of the elements. - * ```typescript - * let a = UInt64.from(5); // ... 000101 - * let b = UInt64.from(3); // ... 000011 - * - * let c = a.xor(b); // ... 000110 - * c.assertEquals(6); - * ``` - */ - xor(y: UInt64) { - return new UInt64(this.value.xor(y.value, UInt64.NUM_BITS)); - } - /** * Integer division with remainder. * @@ -474,21 +459,6 @@ class UInt32 extends CircuitValue { return new UInt32(Field((1n << 32n) - 1n)); } - /** - * Bitwise XOR gate on {@link UInt32} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). - * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. It applies XOR to all 32 bits of the elements. - * ```typescript - * let a = UInt32.from(5); // ... 000101 - * let b = UInt32.from(3); // ... 000011 - * - * let c = a.xor(b); // ... 000110 - * c.assertEquals(6); - * ``` - */ - xor(y: UInt32) { - return new UInt32(this.value.xor(y.value, UInt32.NUM_BITS)); - } - /** * Integer division with remainder. * From 8f9c9a865326d7c20caa5857272dcad1694daed9 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:13:36 +0200 Subject: [PATCH 0231/1215] move gadget tests --- src/lib/field.unit-test.ts | 20 -------------------- src/lib/gadgets/bitwise.unit-test.ts | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 9ae4ee945a..8f7d8843f5 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -17,7 +17,6 @@ import { bool, Spec, } from './testing/equivalent.js'; -import { Gadgets } from './gadgets/gadgets.js'; // types Field satisfies Provable; @@ -66,25 +65,6 @@ test(Random.field, Random.int(-5, 5), (x, k) => { deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); }); -// XOR with some common and odd lengths -[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { - test(Random.field, Random.field, (x_, y_, assert) => { - let modulus = 1n << BigInt(length); - let x = x_ % modulus; - let y = y_ % modulus; - let z = Field(x); - - let r1 = Fp.xor(BigInt(x), BigInt(y)); - - Provable.runAndCheck(() => { - let zz = Provable.witness(Field, () => z); - let yy = Provable.witness(Field, () => Field(y)); - let r2 = Gadgets.xor(zz, yy, length); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - }); -}); - // Field | bigint parameter let fieldOrBigint = oneOf(field, bigintField); diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index d18d4bbe5d..f7c628109f 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -1,9 +1,10 @@ import { ZkProgram } from '../proof_system.js'; import { Spec, equivalentAsync, field } from '../testing/equivalent.js'; -import { Random } from '../testing/random.js'; import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; +import { Provable } from '../provable.js'; +import { test, Random } from '../testing/property.js'; let Bitwise = ZkProgram({ publicOutput: Field, @@ -26,6 +27,25 @@ let maybeUint64: Spec = { ), }; +// XOR with some common and odd lengths +[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { + test(Random.field, Random.field, (x_, y_, assert) => { + let modulus = 1n << BigInt(length); + let x = x_ % modulus; + let y = y_ % modulus; + let z = new Field(x); + + let r1 = Fp.xor(BigInt(x), BigInt(y)); + + Provable.runAndCheck(() => { + let zz = Provable.witness(Field, () => z); + let yy = Provable.witness(Field, () => new Field(y)); + let r2 = Gadgets.xor(zz, yy, length); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + }); +}); + // do a couple of proofs equivalentAsync({ from: [maybeUint64, maybeUint64], to: field }, { runs: 3 })( (x, y) => { From f3505cf1ace4f025480dec6a256974a8ad20e323 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:15:33 +0200 Subject: [PATCH 0232/1215] cleanup --- src/lib/field.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 6b9ff9082e..7ff78ce4e5 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -5,7 +5,6 @@ import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; -import { Gadgets } from './gadgets/gadgets.js'; // external API export { Field }; From 08c5d10c1a857c809df2d960463ddee0baecda9a Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:16:59 +0200 Subject: [PATCH 0233/1215] cleanup example --- src/examples/gadgets.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 608cdee2f7..2048b294fa 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,27 +1,5 @@ import { Field, Provable, Gadgets, Experimental } from 'o1js'; -Provable.runAndCheck(() => { - let res = Gadgets.xor( - Field(521515), - Provable.witness(Field, () => Field(771812)), - 32 - ); - Provable.log(res); -}); - -let res = Gadgets.xor(Field(2), Field(5), 4); -Provable.log(res); - -let cs = Provable.constraintSystem(() => { - let res = Gadgets.xor( - Provable.witness(Field, () => Field(5215)), - Provable.witness(Field, () => Field(7812)), - 2 - ); - Provable.log(res); -}); -console.log(cs); - const XOR = Experimental.ZkProgram({ methods: { baseCase: { From 92de8ecd357d706779a30622f8e07812871a3ef4 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:39:38 +0200 Subject: [PATCH 0234/1215] clean up unit test --- src/lib/gadgets/bitwise.unit-test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f7c628109f..00f0699243 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,13 +20,6 @@ let Bitwise = ZkProgram({ await Bitwise.compile(); -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), -}; - // XOR with some common and odd lengths [2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { test(Random.field, Random.field, (x_, y_, assert) => { @@ -46,9 +39,18 @@ let maybeUint64: Spec = { }); }); +let maybeUint64: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + // do a couple of proofs equivalentAsync({ from: [maybeUint64, maybeUint64], to: field }, { runs: 3 })( (x, y) => { + if (x > 2 ** length || y > 2 ** length) + throw Error('Does not fit into 64 bits'); return Fp.xor(x, y); }, async (x, y) => { From 8991e3b2e53e6f77e1cdc8d79e1bc1caeba2c16a Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 14:51:45 +0200 Subject: [PATCH 0235/1215] fix unit test --- src/lib/gadgets/bitwise.unit-test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 00f0699243..32373efe5c 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -49,8 +49,7 @@ let maybeUint64: Spec = { // do a couple of proofs equivalentAsync({ from: [maybeUint64, maybeUint64], to: field }, { runs: 3 })( (x, y) => { - if (x > 2 ** length || y > 2 ** length) - throw Error('Does not fit into 64 bits'); + if (x > 2 ** 64 || y > 2 ** 64) throw Error('Does not fit into 64 bits'); return Fp.xor(x, y); }, async (x, y) => { From cc2d15882b68250d80c20154814c6da23d08a123 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 09:24:07 -0700 Subject: [PATCH 0236/1215] refactor(gadgets.ts): change testRot function parameters from number to string to avoid precision loss in JavaScript --- src/examples/gadgets.ts | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index cba262e689..06c6c41b79 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -18,20 +18,26 @@ function testRot( } console.log('Running positive tests...'); -testRot(Field(0), 0, 'left', Field(0)); -testRot(Field(0), 32, 'right', Field(0)); -testRot(Field(1), 1, 'left', Field(2)); -testRot(Field(1), 63, 'left', Field(9223372036854775808)); -testRot(Field(256), 4, 'right', Field(16)); - -// TODO: fix this test -// 0x5A5A5A5A5A5A5A5A is 0xA5A5A5A5A5A5A5A5 both when rotate 4 bits left or right -// testRot(Field(6510615555426900570), 4, 'right', Field(11936128518282651045)); -//testRot(Field(6510615555426900570), 4, 'right', Field(11936128518282651045)); - -testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); -testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); -testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); +testRot(Field('0'), 0, 'left', Field('0')); +testRot(Field('0'), 32, 'right', Field('0')); +testRot(Field('1'), 1, 'left', Field('2')); +testRot(Field('1'), 63, 'left', Field('9223372036854775808')); +testRot(Field('256'), 4, 'right', Field('16')); +testRot(Field('1234567890'), 32, 'right', Field('5302428712241725440')); +testRot(Field('2651214356120862720'), 32, 'right', Field('617283945')); +testRot(Field('1153202983878524928'), 32, 'right', Field('268500993')); +testRot( + Field('6510615555426900570'), + 4, + 'right', + Field('11936128518282651045') +); +testRot( + Field('6510615555426900570'), + 4, + 'right', + Field('11936128518282651045') +); console.log('🎉 Passed positive tests'); let cs = Provable.constraintSystem(() => { From b7698a6c85b05005fa29fc203abdb1b1e4f5738c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 09:24:21 -0700 Subject: [PATCH 0237/1215] refactor(rot.ts): update comment for clarity on the role of the prover in checking the input word length --- src/lib/gadgets/rot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 1a99afcc8e..d651f05cae 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -27,7 +27,7 @@ function rotate( ); } - // Check that the input word is at most 64 bits. + // Check as the prover, that the input word is at most 64 bits. Provable.asProver(() => { if (word.toBigInt() > 2 ** MAX_BITS) { throw Error( From 1c25be89d277083238bad555b5395765ee08ae00 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 17 Oct 2023 18:34:24 +0200 Subject: [PATCH 0238/1215] address feedback --- src/bindings | 2 +- src/lib/field.ts | 4 +-- src/lib/gadgets/bitwise.ts | 38 +++++----------------------- src/lib/gadgets/bitwise.unit-test.ts | 32 +++++++++++------------ src/lib/gates.ts | 6 ++--- src/snarky.d.ts | 2 +- 6 files changed, 29 insertions(+), 55 deletions(-) diff --git a/src/bindings b/src/bindings index f8185abae7..571d19674a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f8185abae79db6dcb01212e57c4871052c09a1be +Subproject commit 571d19674a4dd6ec8f13615dedc830cba138dc39 diff --git a/src/lib/field.ts b/src/lib/field.ts index 7ff78ce4e5..5d810ef20f 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1246,9 +1246,9 @@ class Field { /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. * - * As all {@link Field} elements have 31 bytes, this function returns 31. + * As all {@link Field} elements have 32 bytes, this function returns 32. * - * @return The size of a {@link Field} element - 31. + * @return The size of a {@link Field} element - 32. */ static sizeInBytes() { return Fp.sizeInBytes(); diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 5149c52913..040d382993 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,27 +5,6 @@ import * as Gates from '../gates.js'; export { xor }; -/** - * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). - * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. - * - * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. - * - * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. - * - * **Note:** Specifying a larger `length` parameter adds additional constraints. - * - * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. - * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. - * - * ```typescript - * let a = Field(5); // ... 000101 - * let b = Field(3); // ... 000011 - * - * let c = xor(a, b, 2); // ... 000110 - * c.assertEquals(6); - * ``` - */ function xor(a: Field, b: Field, length: number, lengthXor = 4) { // check that both input lengths are positive assert( @@ -39,8 +18,8 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); - // sanity check as prover to check that both elements fit into length bits - Provable.asProver(() => { + // handle constant case + if (a.isConstant() && b.isConstant()) { assert( a.toBigInt() < 2 ** length, `${a.toBigInt()} does not fit into ${length} bits` @@ -50,10 +29,7 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { b.toBigInt() < 2 ** length, `${b.toBigInt()} does not fit into ${length} bits` ); - }); - // handle constant case - if (a.isConstant() && b.isConstant()) { return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } @@ -64,10 +40,8 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { ); // obtain pad length until the length is a multiple of 4*n for n-bit length lookup table - let padLength = length; - if (length % (4 * lengthXor) !== 0) { - padLength = length + 4 * lengthXor - (length % (4 * lengthXor)); - } + let l = 4 * lengthXor; + let padLength = Math.ceil(length / l) * l; // recursively build xor gadget chain buildXor(a, b, outputXor, padLength, lengthXor); @@ -86,14 +60,14 @@ function buildXor( ) { // if inputs are zero and length is zero, add the zero check if (padLength === 0) { - Gates.zeroCheck(a, b, expectedOutput); + Gates.zero(a, b, expectedOutput); let zero = new Field(0); zero.assertEquals(a); zero.assertEquals(b); zero.assertEquals(expectedOutput); } else { - // nibble offsets + // lengthXor-sized offsets let first = lengthXor; let second = first + lengthXor; let third = second + lengthXor; diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 32373efe5c..4b218729b0 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -1,5 +1,10 @@ import { ZkProgram } from '../proof_system.js'; -import { Spec, equivalentAsync, field } from '../testing/equivalent.js'; +import { + Spec, + equivalent, + equivalentAsync, + field, +} from '../testing/equivalent.js'; import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; @@ -21,24 +26,19 @@ let Bitwise = ZkProgram({ await Bitwise.compile(); // XOR with some common and odd lengths -[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { - test(Random.field, Random.field, (x_, y_, assert) => { - let modulus = 1n << BigInt(length); - let x = x_ % modulus; - let y = y_ % modulus; - let z = new Field(x); - - let r1 = Fp.xor(BigInt(x), BigInt(y)); +let uint = (length: number) => fieldWithRng(Random.biguint(length)); - Provable.runAndCheck(() => { - let zz = Provable.witness(Field, () => z); - let yy = Provable.witness(Field, () => new Field(y)); - let r2 = Gadgets.xor(zz, yy, length); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - }); +[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { + equivalent({ from: [uint(length), uint(length)], to: field })( + Fp.xor, + (x, y) => Gadgets.xor(x, y, length) + ); }); +// helper that should be added to `equivalent.ts` +function fieldWithRng(rng: Random): Spec { + return { ...field, rng }; +} let maybeUint64: Spec = { ...field, rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 354df690b7..1a5a9a3c00 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64, xor, zeroCheck }; +export { rangeCheck64, xor, zero }; /** * Asserts that x is at most 64 bits @@ -81,8 +81,8 @@ function xor( ); } -function zeroCheck(a: Field, b: Field, c: Field) { - Snarky.gates.zeroCheck(a.value, b.value, c.value); +function zero(a: Field, b: Field, c: Field) { + Snarky.gates.zero(a.value, b.value, c.value); } function getBits(x: bigint, start: number, length: number) { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d259ac5ab6..ab0f5e85a9 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -325,7 +325,7 @@ declare const Snarky: { out_3: FieldVar ): void; - zeroCheck(in1: FieldVar, in2: FieldVar, out: FieldVar): void; + zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; }; bool: { From 1e1f46e102fb724b5d3736d2d4e11741fe3c6ff3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 09:39:36 -0700 Subject: [PATCH 0239/1215] chore(bindings): update subproject commit hash to latest version for up-to-date dependencies --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 13cb203feb..62ebd762bb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 13cb203febb168a47c82fa83013e7e4b4193d438 +Subproject commit 62ebd762bbc3f5698ec01f202d36a97a4689d375 From b7b01a3f826f13d46d6213b7d41b09ef267806e1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 09:44:53 -0700 Subject: [PATCH 0240/1215] feat(gadgets.ts): add rot function to Gadgets namespace for bit rotation The rot function provides left and right bit rotation functionality. It accepts a Field element, the number of bits to rotate, and the direction of rotation. This function will throw an error if the Field element exceeds 64 bits. --- src/lib/gadgets/gadgets.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b4c799b2cb..3566a2d2f1 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,6 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; +import { rot } from './rot.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -33,4 +34,27 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, + + /** + * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. + * + * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. + * + * @param word {@link Field} element to rotate. + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```typescript + * let a = Field(12); + * let b = a.rot(2, 'left'); // left rotation by 2 bit + * b.assertEquals(48); + * ``` + */ + rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rot(word, bits, direction); + }, }; From 3f1de405f6056cf07445448bdd62336eab838d8d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 09:45:54 -0700 Subject: [PATCH 0241/1215] docs(field.ts, int.ts): rearrange and add more details to the rot method documentation --- src/lib/field.ts | 9 ++++++--- src/lib/int.ts | 32 ++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 6471d02456..089b11d53c 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -596,14 +596,17 @@ class Field { * * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. * + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example * ```typescript * let a = Field(12); * let b = a.rot(2, 'left'); // left rotation by 2 bit * b.assertEquals(48); * ``` - * - * @param bits amount of bits to rotate this {@link Field} element with. - * @param direction left or right rotation direction. */ rot(bits: number = 64, direction: 'left' | 'right' = 'left') { if (this.isConstant()) { diff --git a/src/lib/int.ts b/src/lib/int.ts index 6ea5f4dd75..7f348eb06a 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -374,15 +374,21 @@ class UInt64 extends CircuitValue { /** * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. + * + * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. + * + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example * ```typescript - * let a = UInt64.from(12); + * let a = Field(12); * let b = a.rot(2, 'left'); // left rotation by 2 bit * b.assertEquals(48); * ``` - * - * @param bits amount of bits to rotate this {@link UInt64} element with. - * @param direction left or right rotation direction. */ rot(bits: number = UInt64.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt64(this.value.rot(bits, direction)); @@ -726,15 +732,21 @@ class UInt32 extends CircuitValue { /** * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * + * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. + * + * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. + * + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example * ```typescript - * let a = UInt32.from(12); + * let a = Field(12); * let b = a.rot(2, 'left'); // left rotation by 2 bit * b.assertEquals(48); * ``` - * - * @param bits amount of bits to rotate this {@link UInt64} element with. - * @param direction left or right rotation direction. */ rot(bits: number = UInt32.NUM_BITS, direction: 'left' | 'right' = 'left') { return new UInt32(this.value.rot(bits, direction)); From c0210c3cc9cb69d0d83429deecb70116547168a9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:03:48 -0700 Subject: [PATCH 0242/1215] feat(gadgets.unit-test.ts): add ROT ZkProgram and its equivalentAsync test --- src/lib/gadgets/gadgets.unit-test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 13a44a059d..aec2477b1b 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -23,7 +23,20 @@ let RangeCheck64 = ZkProgram({ }, }); +let ROT = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(x) { + Gadgets.rot(x, 2, 'left'); + Gadgets.rot(x, 2, 'right'); + }, + }, + }, +}); + await RangeCheck64.compile(); +await ROT.compile(); let maybeUint64: Spec = { ...field, @@ -45,3 +58,14 @@ equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( return await RangeCheck64.verify(proof); } ); + +equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( + (x) => { + if (x >= 1n << 64n) throw Error('expected 64 bits'); + return true; + }, + async (x) => { + let proof = await ROT.run(x); + return await ROT.verify(proof); + } +); From 22050e10da62e6f6218e3769318a156e1f993cc9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:06:49 -0700 Subject: [PATCH 0243/1215] feat: add rot tests to primitive_constraint_system and vk_regression --- src/examples/primitive_constraint_system.ts | 13 ++++++++++++- src/examples/vk_regression.ts | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 17735faa04..961ff1788b 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -1,4 +1,4 @@ -import { Field, Group, Poseidon, Provable, Scalar } from 'o1js'; +import { Field, Group, Poseidon, Gadgets, Provable, Scalar } from 'o1js'; function mock(obj: { [K: string]: (...args: any) => void }, name: string) { let methodKeys = Object.keys(obj); @@ -63,4 +63,15 @@ const GroupMock = { }, }; +const BitwiseMock = { + rot() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rot(a, 2, 'left'); + Gadgets.rot(a, 2, 'right'); + Gadgets.rot(a, 4, 'left'); + Gadgets.rot(a, 4, 'left'); + }, +}; + export const GroupCS = mock(GroupMock, 'Group Primitive'); +export const BitwiseCS = mock(BitwiseMock, 'Bitwise Primitive'); diff --git a/src/examples/vk_regression.ts b/src/examples/vk_regression.ts index 6dbcd31373..a76b41aef4 100644 --- a/src/examples/vk_regression.ts +++ b/src/examples/vk_regression.ts @@ -3,7 +3,7 @@ import { Voting_ } from './zkapps/voting/voting.js'; import { Membership_ } from './zkapps/voting/membership.js'; import { HelloWorld } from './zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from './zkapps/dex/dex.js'; -import { GroupCS } from './primitive_constraint_system.js'; +import { GroupCS, BitwiseCS } from './primitive_constraint_system.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; @@ -37,6 +37,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ TokenContract, createDex().Dex, GroupCS, + BitwiseCS, ]; let filePath = jsonPath ? jsonPath : './src/examples/regression_test.json'; From ea73c6a457efb2376156708b099c774f904d50c1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:08:38 -0700 Subject: [PATCH 0244/1215] feat(regression_test.json): add 'Bitwise Primitive' test case to extend test coverage --- src/examples/regression_test.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index e492f2d163..f664f15780 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -164,5 +164,18 @@ "data": "", "hash": "" } + }, + "Bitwise Primitive": { + "digest": "Bitwise Primitive", + "methods": { + "rot": { + "rows": 13, + "digest": "a84b4cdef2d61ddab70f47b788d096d4" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } } } \ No newline at end of file From 95a4559781bf047fc2644173e44289b9a2ce85c8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:09:25 -0700 Subject: [PATCH 0245/1215] Revert "feat(field.ts, int.ts): add default values for bits parameter in rot method to improve usability" This reverts commit e0bcb0f6a43703e860500101967a419f80728d53. --- src/lib/field.ts | 2 +- src/lib/int.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 089b11d53c..44ad0ad6b1 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -608,7 +608,7 @@ class Field { * b.assertEquals(48); * ``` */ - rot(bits: number = 64, direction: 'left' | 'right' = 'left') { + rot(bits: number, direction: 'left' | 'right' = 'left') { if (this.isConstant()) { return new Field(Fp.rot(this.toBigInt(), bits, direction === 'left')); } else { diff --git a/src/lib/int.ts b/src/lib/int.ts index 7f348eb06a..25bc76bc28 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -390,7 +390,7 @@ class UInt64 extends CircuitValue { * b.assertEquals(48); * ``` */ - rot(bits: number = UInt64.NUM_BITS, direction: 'left' | 'right' = 'left') { + rot(bits: number, direction: 'left' | 'right' = 'left') { return new UInt64(this.value.rot(bits, direction)); } } @@ -748,7 +748,7 @@ class UInt32 extends CircuitValue { * b.assertEquals(48); * ``` */ - rot(bits: number = UInt32.NUM_BITS, direction: 'left' | 'right' = 'left') { + rot(bits: number, direction: 'left' | 'right' = 'left') { return new UInt32(this.value.rot(bits, direction)); } } From 8f53db3ea0a24bcdcdf49fd2c0431beae1e15012 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:31:27 -0700 Subject: [PATCH 0246/1215] feat(rot): fix unit tests by only using Gadgets namespace for public API --- src/examples/gadgets.ts | 18 ++++++++++------ src/lib/field.ts | 27 ----------------------- src/lib/field.unit-test.ts | 7 +++--- src/lib/gadgets/gadgets.ts | 10 +++++---- src/lib/gadgets/rot.ts | 4 ++++ src/lib/int.ts | 44 -------------------------------------- 6 files changed, 25 insertions(+), 85 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 06c6c41b79..21a9c0f48b 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,4 +1,4 @@ -import { Field, Provable, Experimental } from 'o1js'; +import { Field, Provable, Experimental, Gadgets } from 'o1js'; function testRot( word: Field, @@ -9,7 +9,7 @@ function testRot( Provable.runAndCheck(() => { let w = Provable.witness(Field, () => word); let r = Provable.witness(Field, () => result); - let output = w.rot(bits, mode); + let output = Gadgets.rot(w, bits, mode); Provable.asProver(() => { Provable.log(`rot(${word}, ${bits}, ${mode}) = ${output}`); }); @@ -41,8 +41,14 @@ testRot( console.log('🎉 Passed positive tests'); let cs = Provable.constraintSystem(() => { - let res1 = Provable.witness(Field, () => Field(12).rot(2, 'left')); - let res2 = Provable.witness(Field, () => Field(12).rot(2, 'right')); + let res1 = Provable.witness(Field, () => { + let f = Field(12); + return Gadgets.rot(f, 2, 'left'); + }); + let res2 = Provable.witness(Field, () => { + let f = Field(12); + return Gadgets.rot(f, 2, 'right'); + }); res1.assertEquals(Field(48)); res2.assertEquals(Field(3)); @@ -58,8 +64,8 @@ const ROT = Experimental.ZkProgram({ privateInputs: [], method: () => { let a = Provable.witness(Field, () => Field(48)); - let actualLeft = a.rot(2, 'left'); - let actualRight = a.rot(2, 'right'); + let actualLeft = Gadgets.rot(a, 2, 'left'); + let actualRight = Gadgets.rot(a, 2, 'right'); let expectedLeft = Field(192); actualLeft.assertEquals(expectedLeft); diff --git a/src/lib/field.ts b/src/lib/field.ts index 44ad0ad6b1..29193d64db 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -5,7 +5,6 @@ import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; -import { rot } from './gadgets/rot.js'; // external API export { Field }; @@ -590,32 +589,6 @@ class Field { return new Field(z); } - /** - * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. - * - * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. - * - * @param bits amount of bits to rotate this {@link Field} element with. - * @param direction left or right rotation direction. - * - * @throws Throws an error if the input value exceeds 64 bits. - * - * @example - * ```typescript - * let a = Field(12); - * let b = a.rot(2, 'left'); // left rotation by 2 bit - * b.assertEquals(48); - * ``` - */ - rot(bits: number, direction: 'left' | 'right' = 'left') { - if (this.isConstant()) { - return new Field(Fp.rot(this.toBigInt(), bits, direction === 'left')); - } else { - return rot(this, bits, direction); - } - } - /** * @deprecated use `x.equals(0)` which is equivalent */ diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 6706e7b7fd..28e17b90eb 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -17,6 +17,7 @@ import { bool, Spec, } from './testing/equivalent.js'; +import { Gadgets } from './gadgets/gadgets.js'; // types Field satisfies Provable; @@ -53,10 +54,8 @@ test( let z = Field(x); let r1 = Fp.rot(x, n, direction); Provable.runAndCheck(() => { - let r2 = Provable.witness(Field, () => z).rot( - n, - direction ? 'left' : 'right' - ); + let f = Provable.witness(Field, () => z); + let r2 = Gadgets.rot(f, n, direction ? 'left' : 'right'); Provable.asProver(() => assert(r1 === r2.toBigInt())); }); } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 3566a2d2f1..bf13001128 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -48,10 +48,12 @@ const Gadgets = { * @throws Throws an error if the input value exceeds 64 bits. * * @example - * ```typescript - * let a = Field(12); - * let b = a.rot(2, 'left'); // left rotation by 2 bit - * b.assertEquals(48); + * ```ts + * const x = Provable.witness(Field, () => Field(12)); + * const y = rot(x, 2, 'left'); // left rotation by 2 bit + * const z = rot(x, 2, 'right'); // right rotation by 2 bit + * y.assertEquals(48); + * z.assertEquals(3) * ``` */ rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index d651f05cae..e7763b7ff1 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -1,5 +1,6 @@ import { Field } from '../field.js'; import { Provable } from '../provable.js'; +import { Fp } from '../../bindings/crypto/finite_field.js'; import * as Gates from '../gates.js'; export { rot, rotate }; @@ -7,6 +8,9 @@ export { rot, rotate }; const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { + if (word.isConstant()) { + return new Field(Fp.rot(word.toBigInt(), bits, direction === 'left')); + } const [rotated] = rotate(word, bits, direction); return rotated; } diff --git a/src/lib/int.ts b/src/lib/int.ts index 25bc76bc28..f4143a9a5b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -371,28 +371,6 @@ class UInt64 extends CircuitValue { assertGreaterThanOrEqual(y: UInt64, message?: string) { y.assertLessThanOrEqual(this, message); } - - /** - * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. - * - * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. - * - * @param bits amount of bits to rotate this {@link Field} element with. - * @param direction left or right rotation direction. - * - * @throws Throws an error if the input value exceeds 64 bits. - * - * @example - * ```typescript - * let a = Field(12); - * let b = a.rot(2, 'left'); // left rotation by 2 bit - * b.assertEquals(48); - * ``` - */ - rot(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt64(this.value.rot(bits, direction)); - } } /** * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. @@ -729,28 +707,6 @@ class UInt32 extends CircuitValue { assertGreaterThanOrEqual(y: UInt32, message?: string) { y.assertLessThanOrEqual(this, message); } - - /** - * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. - * - * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. - * - * @param bits amount of bits to rotate this {@link Field} element with. - * @param direction left or right rotation direction. - * - * @throws Throws an error if the input value exceeds 64 bits. - * - * @example - * ```typescript - * let a = Field(12); - * let b = a.rot(2, 'left'); // left rotation by 2 bit - * b.assertEquals(48); - * ``` - */ - rot(bits: number, direction: 'left' | 'right' = 'left') { - return new UInt32(this.value.rot(bits, direction)); - } } class Sign extends CircuitValue { From d86a4c81adc4a89cc3929a83a84b53fe52ee1a88 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:39:47 -0700 Subject: [PATCH 0247/1215] chore(rot): minor doc and vk updates --- src/examples/primitive_constraint_system.ts | 2 +- src/examples/regression_test.json | 2 +- src/lib/gadgets/gadgets.ts | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 961ff1788b..c680a1855b 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -69,7 +69,7 @@ const BitwiseMock = { Gadgets.rot(a, 2, 'left'); Gadgets.rot(a, 2, 'right'); Gadgets.rot(a, 4, 'left'); - Gadgets.rot(a, 4, 'left'); + Gadgets.rot(a, 4, 'right'); }, }; diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index f664f15780..a4eadbd725 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -170,7 +170,7 @@ "methods": { "rot": { "rows": 13, - "digest": "a84b4cdef2d61ddab70f47b788d096d4" + "digest": "2c0dadbba96fd7ddb9adb7d643425ce3" } }, "verificationKey": { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index bf13001128..01fe77b052 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -50,10 +50,13 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(12)); - * const y = rot(x, 2, 'left'); // left rotation by 2 bit - * const z = rot(x, 2, 'right'); // right rotation by 2 bit + * const y = rot(x, 2, 'left'); // left rotation by 2 bits + * const z = rot(x, 2, 'right'); // right rotation by 2 bits * y.assertEquals(48); * z.assertEquals(3) + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits * ``` */ rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { From 39328bfec96a953376d998d44d3f43d1176310a1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 10:53:13 -0700 Subject: [PATCH 0248/1215] chore(bindings): update bindings for compiled ROT artifacts --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 62ebd762bb..038a16eac1 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 62ebd762bbc3f5698ec01f202d36a97a4689d375 +Subproject commit 038a16eac1bf5dc62779a079b95fa36e21aa4202 From 300427cb1de6ce36903f9996234e745db5f68947 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 11:12:56 -0700 Subject: [PATCH 0249/1215] feat(rot): minor refactor to rot gate checks and add range check to rotated word --- src/lib/gadgets/rot.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index e7763b7ff1..c1763d1cbb 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -8,6 +8,17 @@ export { rot, rotate }; const MAX_BITS = 64 as const; function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { + // Check that the rotation bits are in range + if (bits < 0 || bits > MAX_BITS) { + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); + } + + if (direction !== 'left' && direction !== 'right') { + throw Error( + `rot: expected direction to be 'left' or 'right', got ${direction}` + ); + } + if (word.isConstant()) { return new Field(Fp.rot(word.toBigInt(), bits, direction === 'left')); } @@ -20,17 +31,6 @@ function rotate( bits: number, direction: 'left' | 'right' = 'left' ): [Field, Field, Field] { - // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); - } - - if (direction !== 'left' && direction !== 'right') { - throw Error( - `rot: expected direction to be 'left' or 'right', got ${direction}` - ); - } - // Check as the prover, that the input word is at most 64 bits. Provable.asProver(() => { if (word.toBigInt() > 2 ** MAX_BITS) { @@ -41,7 +41,7 @@ function rotate( }); const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; - const big2Power64 = 2n ** 64n; + const big2Power64 = 2n ** BigInt(MAX_BITS); const big2PowerRot = 2n ** BigInt(rotationBits); const [rotated, excess, shifted, bound] = Provable.witness( @@ -92,6 +92,7 @@ function rotate( Gates.rangeCheck64(shifted); // Compute following row Gates.rangeCheck64(excess); + Gates.rangeCheck64(rotated); return [rotated, excess, shifted]; } From cd2c3444202fe515edebb353a2f55ec131d96317 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 11:17:15 -0700 Subject: [PATCH 0250/1215] fix(gadgets.unit-test.ts): add await keyword to equivalentAsync function calls to ensure tests run asynchronously and complete before proceeding --- src/lib/gadgets/gadgets.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index aec2477b1b..11def1e147 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -48,7 +48,7 @@ let maybeUint64: Spec = { // do a couple of proofs // TODO: we use this as a test because there's no way to check custom gates quickly :( -equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); return true; @@ -59,7 +59,7 @@ equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( } ); -equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); return true; From 7de9b160346ad499badc9981536f7647d0e152e0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 17 Oct 2023 11:35:17 -0700 Subject: [PATCH 0251/1215] fix(regression_test.json): update 'rows' and 'digest' values in 'rot' method to reflect recent changes in the test data --- src/examples/regression_test.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index a4eadbd725..ef26739ab3 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -169,8 +169,8 @@ "digest": "Bitwise Primitive", "methods": { "rot": { - "rows": 13, - "digest": "2c0dadbba96fd7ddb9adb7d643425ce3" + "rows": 17, + "digest": "5253728d9fd357808be58a39c8375c07" } }, "verificationKey": { From 598bdd00dd6855f3949fd03178975d10365b689a Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 13:39:32 +0200 Subject: [PATCH 0252/1215] address feedback --- src/lib/gadgets/bitwise.ts | 149 ++++++++++++------------------------- src/lib/gadgets/gadgets.ts | 6 +- 2 files changed, 51 insertions(+), 104 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 5149c52913..fe0bf15107 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,33 +5,11 @@ import * as Gates from '../gates.js'; export { xor }; -/** - * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). - * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. - * - * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. - * - * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. - * - * **Note:** Specifying a larger `length` parameter adds additional constraints. - * - * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. - * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. - * - * ```typescript - * let a = Field(5); // ... 000101 - * let b = Field(3); // ... 000011 - * - * let c = xor(a, b, 2); // ... 000110 - * c.assertEquals(6); - * ``` - */ -function xor(a: Field, b: Field, length: number, lengthXor = 4) { +const LENGTH_XOR = 4; + +function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive - assert( - length > 0 && lengthXor > 0, - `Input lengths need to be positive values.` - ); + assert(length > 0, `Input lengths need to be positive values.`); // check that length does not exceed maximum field size in bits assert( @@ -64,13 +42,11 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { ); // obtain pad length until the length is a multiple of 4*n for n-bit length lookup table - let padLength = length; - if (length % (4 * lengthXor) !== 0) { - padLength = length + 4 * lengthXor - (length % (4 * lengthXor)); - } + let l = 4 * LENGTH_XOR; + let padLength = Math.ceil(length / l) * l; // recursively build xor gadget chain - buildXor(a, b, outputXor, padLength, lengthXor); + buildXor(a, b, outputXor, padLength); // return the result of the xor operation return outputXor; @@ -81,41 +57,33 @@ function buildXor( a: Field, b: Field, expectedOutput: Field, - padLength: number, - lengthXor: number + padLength: number ) { - // if inputs are zero and length is zero, add the zero check - if (padLength === 0) { - Gates.zeroCheck(a, b, expectedOutput); - - let zero = new Field(0); - zero.assertEquals(a); - zero.assertEquals(b); - zero.assertEquals(expectedOutput); - } else { - // nibble offsets - let first = lengthXor; - let second = first + lengthXor; - let third = second + lengthXor; - let fourth = third + lengthXor; - + // 4 bit sized offsets + let first = LENGTH_XOR; + let second = first + LENGTH_XOR; + let third = second + LENGTH_XOR; + + // construct the chain of XORs until padLength is 0 + while (padLength !== 0) { + // slices the inputs into LENGTH_XOR-sized chunks // slices of a - let in1_0 = witnessSlices(a, 0, first); - let in1_1 = witnessSlices(a, first, second); - let in1_2 = witnessSlices(a, second, third); - let in1_3 = witnessSlices(a, third, fourth); + let in1_0 = witnessSlices(a, 0, LENGTH_XOR); + let in1_1 = witnessSlices(a, first, LENGTH_XOR); + let in1_2 = witnessSlices(a, second, LENGTH_XOR); + let in1_3 = witnessSlices(a, third, LENGTH_XOR); // slices of b - let in2_0 = witnessSlices(b, 0, first); - let in2_1 = witnessSlices(b, first, second); - let in2_2 = witnessSlices(b, second, third); - let in2_3 = witnessSlices(b, third, fourth); + let in2_0 = witnessSlices(b, 0, LENGTH_XOR); + let in2_1 = witnessSlices(b, first, LENGTH_XOR); + let in2_2 = witnessSlices(b, second, LENGTH_XOR); + let in2_3 = witnessSlices(b, third, LENGTH_XOR); // slice of expected output - let out0 = witnessSlices(expectedOutput, 0, first); - let out1 = witnessSlices(expectedOutput, first, second); - let out2 = witnessSlices(expectedOutput, second, third); - let out3 = witnessSlices(expectedOutput, third, fourth); + let out0 = witnessSlices(expectedOutput, 0, LENGTH_XOR); + let out1 = witnessSlices(expectedOutput, first, LENGTH_XOR); + let out2 = witnessSlices(expectedOutput, second, LENGTH_XOR); + let out3 = witnessSlices(expectedOutput, third, LENGTH_XOR); // assert that the xor of the slices is correct, 16 bit at a time Gates.xor( @@ -136,20 +104,20 @@ function buildXor( out3 ); - let nextIn1 = witnessNextValue(a, in1_0, in1_1, in1_2, in1_3, lengthXor); - let nextIn2 = witnessNextValue(b, in2_0, in2_1, in2_2, in2_3, lengthXor); - let nextExpectedOutput = witnessNextValue( - expectedOutput, - out0, - out1, - out2, - out3, - lengthXor - ); - - let next_length = padLength - 4 * lengthXor; - buildXor(nextIn1, nextIn2, nextExpectedOutput, next_length, lengthXor); + // update the values for the next loop iteration + a = witnessNextValue(a); + b = witnessNextValue(b); + expectedOutput = witnessNextValue(expectedOutput); + padLength = padLength - 4 * LENGTH_XOR; } + + // inputs are zero and length is zero, add the zero check - we reached the end of our chain + Gates.zeroCheck(a, b, expectedOutput); + + let zero = new Field(0); + zero.assertEquals(a); + zero.assertEquals(b); + zero.assertEquals(expectedOutput); } function assert(stmt: boolean, message?: string) { @@ -158,38 +126,15 @@ function assert(stmt: boolean, message?: string) { } } -function witnessSlices(f: Field, start: number, stop = -1) { - if (stop !== -1 && stop <= start) - throw Error('stop offset must be greater than start offset'); +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); return Provable.witness(Field, () => { - let bits = f.toBits(); - if (stop > bits.length) throw Error('stop must be less than bit-length'); - if (stop === -1) stop = bits.length; - - return Field.fromBits(bits.slice(start, stop)); + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); }); } -function witnessNextValue( - current: Field, - var0: Field, - var1: Field, - var2: Field, - var3: Field, - lenXor: number -) { - return Provable.witness(Field, () => { - let twoPowLen = new Field(2 ** lenXor); - let twoPow2Len = twoPowLen.mul(twoPowLen); - let twoPow3Len = twoPow2Len.mul(twoPowLen); - let twoPow4Len = twoPow3Len.mul(twoPowLen); - - return current - .sub(var0) - .sub(var1.mul(twoPowLen)) - .sub(var2.mul(twoPow2Len)) - .sub(var3.mul(twoPow3Len)) - .div(twoPow4Len); - }); +function witnessNextValue(current: Field) { + return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 528da1f64e..7691d7460a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -39,6 +39,8 @@ const Gadgets = { * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. * + * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. + * * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. * * **Note:** Specifying a larger `length` parameter adds additional constraints. @@ -54,7 +56,7 @@ const Gadgets = { * c.assertEquals(6); * ``` */ - xor(a: Field, b: Field, length: number, lengthXor = 4) { - return xor(a, b, length, lengthXor); + xor(a: Field, b: Field, length: number) { + return xor(a, b, length); }, }; From 35dfd8a18051f9341906f6804231ed09c740290f Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 13:44:37 +0200 Subject: [PATCH 0253/1215] change zero gate name --- src/bindings | 2 +- src/lib/gadgets/bitwise.ts | 2 +- src/lib/gates.ts | 6 +++--- src/snarky.d.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bindings b/src/bindings index f8185abae7..d4d7b792ee 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f8185abae79db6dcb01212e57c4871052c09a1be +Subproject commit d4d7b792eeee6985e1efb8bb6cdd681e94b2bf86 diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index fe0bf15107..67b70039b5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -112,7 +112,7 @@ function buildXor( } // inputs are zero and length is zero, add the zero check - we reached the end of our chain - Gates.zeroCheck(a, b, expectedOutput); + Gates.zero(a, b, expectedOutput); let zero = new Field(0); zero.assertEquals(a); diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 354df690b7..1a5a9a3c00 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64, xor, zeroCheck }; +export { rangeCheck64, xor, zero }; /** * Asserts that x is at most 64 bits @@ -81,8 +81,8 @@ function xor( ); } -function zeroCheck(a: Field, b: Field, c: Field) { - Snarky.gates.zeroCheck(a.value, b.value, c.value); +function zero(a: Field, b: Field, c: Field) { + Snarky.gates.zero(a.value, b.value, c.value); } function getBits(x: bigint, start: number, length: number) { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d259ac5ab6..ab0f5e85a9 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -325,7 +325,7 @@ declare const Snarky: { out_3: FieldVar ): void; - zeroCheck(in1: FieldVar, in2: FieldVar, out: FieldVar): void; + zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; }; bool: { From 1d4a2dd9e568fc0c6cbdfa915521d931d16f7ba2 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 13:47:32 +0200 Subject: [PATCH 0254/1215] fix merge conflict --- src/lib/gadgets/bitwise.ts | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 6ada9bf296..fc284bdcae 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,13 +5,9 @@ import * as Gates from '../gates.js'; export { xor }; -<<<<<<< HEAD const LENGTH_XOR = 4; function xor(a: Field, b: Field, length: number) { -======= -function xor(a: Field, b: Field, length: number, lengthXor = 4) { ->>>>>>> 1c25be89d277083238bad555b5395765ee08ae00 // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -43,11 +39,7 @@ function xor(a: Field, b: Field, length: number, lengthXor = 4) { ); // obtain pad length until the length is a multiple of 4*n for n-bit length lookup table -<<<<<<< HEAD let l = 4 * LENGTH_XOR; -======= - let l = 4 * lengthXor; ->>>>>>> 1c25be89d277083238bad555b5395765ee08ae00 let padLength = Math.ceil(length / l) * l; // recursively build xor gadget chain @@ -64,27 +56,10 @@ function buildXor( expectedOutput: Field, padLength: number ) { -<<<<<<< HEAD // 4 bit sized offsets let first = LENGTH_XOR; let second = first + LENGTH_XOR; let third = second + LENGTH_XOR; -======= - // if inputs are zero and length is zero, add the zero check - if (padLength === 0) { - Gates.zero(a, b, expectedOutput); - - let zero = new Field(0); - zero.assertEquals(a); - zero.assertEquals(b); - zero.assertEquals(expectedOutput); - } else { - // lengthXor-sized offsets - let first = lengthXor; - let second = first + lengthXor; - let third = second + lengthXor; - let fourth = third + lengthXor; ->>>>>>> 1c25be89d277083238bad555b5395765ee08ae00 // construct the chain of XORs until padLength is 0 while (padLength !== 0) { From a24bc2d7e3321f5e934c125ce5bb1a051932ebf5 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 14:04:30 +0200 Subject: [PATCH 0255/1215] fix tests --- src/lib/gadgets/bitwise.unit-test.ts | 11 +++-------- src/lib/testing/equivalent.ts | 5 +++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4b218729b0..12950672f9 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -4,12 +4,12 @@ import { equivalent, equivalentAsync, field, + fieldWithRng, } from '../testing/equivalent.js'; import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; -import { Provable } from '../provable.js'; -import { test, Random } from '../testing/property.js'; +import { Random } from '../testing/property.js'; let Bitwise = ZkProgram({ publicOutput: Field, @@ -25,20 +25,15 @@ let Bitwise = ZkProgram({ await Bitwise.compile(); -// XOR with some common and odd lengths let uint = (length: number) => fieldWithRng(Random.biguint(length)); -[2, 4, 8, 16, 32, 64, 3, 5, 10, 15].forEach((length) => { +[2, 4, 8, 16, 32, 64, 128].forEach((length) => { equivalent({ from: [uint(length), uint(length)], to: field })( Fp.xor, (x, y) => Gadgets.xor(x, y, length) ); }); -// helper that should be added to `equivalent.ts` -function fieldWithRng(rng: Random): Spec { - return { ...field, rng }; -} let maybeUint64: Spec = { ...field, rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index c19748624e..22c1954a91 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -15,6 +15,7 @@ export { handleErrors, deepEqual as defaultAssertEqual, id, + fieldWithRng, }; export { field, bigintField, bool, boolean, unit }; export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; @@ -240,6 +241,10 @@ let boolean: Spec = { back: id, }; +function fieldWithRng(rng: Random): Spec { + return { ...field, rng }; +} + // helper to ensure two functions throw equivalent errors function handleErrors( From fd91ab1336f5e7a307c4cc59b1e76b7ce3575557 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 14:14:11 +0200 Subject: [PATCH 0256/1215] improve doc comments --- src/lib/gadgets/gadgets.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 7691d7460a..e7fc53ad6c 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -41,12 +41,13 @@ const Gadgets = { * * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. * - * The `length` parameter lets you define how many bits should be compared. The output is not constrained to the length. + * The `length` parameter lets you define how many bits should be compared. * * **Note:** Specifying a larger `length` parameter adds additional constraints. * * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. + * If either input element exceeds the maximum bit length, an error is thrown and no proof can be generated because the method fails to properly constrain the operation. * * ```typescript * let a = Field(5); // ... 000101 From eec0cb8844bee5aba5c04779f11e726671db144b Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 14:19:06 +0200 Subject: [PATCH 0257/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index d4d7b792ee..d35d3d6014 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d4d7b792eeee6985e1efb8bb6cdd681e94b2bf86 +Subproject commit d35d3d6014b730c1a48533e88f8567a59737a09c From 23807239d87e2a5de42d885e4ce146e72192f2d0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 18 Oct 2023 17:54:23 +0200 Subject: [PATCH 0258/1215] update pickles type --- src/lib/ml/base.ts | 12 +++++++++++- src/snarky.d.ts | 14 +++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 1b6b7fd02a..128fe0a9e1 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -1,7 +1,7 @@ /** * This module contains basic methods for interacting with OCaml */ -export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes }; +export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes, MlResult }; // ocaml types @@ -10,6 +10,7 @@ type MlArray = [0, ...T[]]; type MlList = [0, T, 0 | MlList]; type MlOption = 0 | [0, T]; type MlBool = 0 | 1; +type MlResult = [0, T] | [1, E]; /** * js_of_ocaml representation of a byte array, @@ -85,3 +86,12 @@ const MlOption = Object.assign( }, } ); + +const MlResult = { + return(t: T): MlResult { + return [0, t]; + }, + unitError(): MlResult { + return [1, 0]; + }, +}; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index f3e4d1e9e4..0967ff7ec4 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -9,6 +9,7 @@ import type { MlOption, MlBool, MlBytes, + MlResult, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; @@ -596,6 +597,16 @@ declare namespace Pickles { proofsToVerify: MlArray<{ isSelf: true } | { isSelf: false; tag: unknown }>; }; + /** + * Type to configure how Pickles should cache prover keys + */ + type Storable = [ + _: 0, + read: (key: any, path: string) => MlResult, + write: (key: any, value: any, path: string) => MlResult, + canWrite: MlBool + ]; + type Prover = ( publicInput: MlArray, previousProofs: MlArray @@ -626,9 +637,10 @@ declare const Pickles: { */ compile: ( rules: MlArray, - signature: { + config: { publicInputSize: number; publicOutputSize: number; + storable?: Pickles.Storable; overrideWrapDomain?: 0 | 1 | 2; } ) => { From 617d840bd23a1f004d56ded8e6ddb39787ac567d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 18 Oct 2023 17:56:32 +0200 Subject: [PATCH 0259/1215] dummy storable for debugging --- src/lib/proof_system.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 553137d0fd..952f2fe2b9 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -25,7 +25,7 @@ import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; -import { MlArray, MlBool, MlTuple } from './ml/base.js'; +import { MlArray, MlBool, MlResult, MlTuple } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; @@ -540,6 +540,19 @@ async function compileProgram({ let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; + let storable: Pickles.Storable = [ + 0, + function read(key, path) { + console.log('READ', path); + return MlResult.unitError(); + }, + function write(key, path, value) { + console.log('WRITE', path, value); + return MlResult.unitError(); + }, + MlBool(true), + ]; + let { verificationKey, provers, verify, tag } = await prettifyStacktracePromise( withThreadPool(async () => { @@ -549,6 +562,7 @@ async function compileProgram({ result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), + storable, overrideWrapDomain, }); } finally { From ba204bb013205b837ada7dc26a11120e102339e9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 18 Oct 2023 17:57:28 +0200 Subject: [PATCH 0260/1215] make simple zkapp example circuits deterministic --- src/examples/simple_zkapp.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index 95593e4273..f033266e86 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -77,7 +77,9 @@ let zkappKey = PrivateKey.random(); let zkappAddress = zkappKey.toPublicKey(); // a special account that is allowed to pull out half of the zkapp balance, once -let privilegedKey = PrivateKey.random(); +let privilegedKey = PrivateKey.fromBase58( + 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' +); let privilegedAddress = privilegedKey.toPublicKey(); let initialBalance = 10_000_000_000; From 9100656f5b3b36d588f35bd9da63330e65e25eea Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 18 Oct 2023 17:57:44 +0200 Subject: [PATCH 0261/1215] simple zkapp web-compatible version --- src/examples/simple_zkapp.web.ts | 131 +++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/examples/simple_zkapp.web.ts diff --git a/src/examples/simple_zkapp.web.ts b/src/examples/simple_zkapp.web.ts new file mode 100644 index 0000000000..098d651779 --- /dev/null +++ b/src/examples/simple_zkapp.web.ts @@ -0,0 +1,131 @@ +import { + Field, + state, + State, + method, + UInt64, + PrivateKey, + SmartContract, + Mina, + AccountUpdate, + Bool, + PublicKey, +} from 'o1js'; + +const doProofs = true; + +const beforeGenesis = UInt64.from(Date.now()); + +class SimpleZkapp extends SmartContract { + @state(Field) x = State(); + + events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; + + @method init() { + super.init(); + this.x.set(initialState); + } + + @method update(y: Field): Field { + this.account.provedState.assertEquals(Bool(true)); + this.network.timestamp.assertBetween(beforeGenesis, UInt64.MAXINT()); + this.emitEvent('update', y); + let x = this.x.get(); + this.x.assertEquals(x); + let newX = x.add(y); + this.x.set(newX); + return newX; + } + + /** + * This method allows a certain privileged account to claim half of the zkapp balance, but only once + * @param caller the privileged account + */ + @method payout(caller: PrivateKey) { + this.account.provedState.assertEquals(Bool(true)); + + // check that caller is the privileged account + let callerAddress = caller.toPublicKey(); + callerAddress.assertEquals(privilegedAddress); + + // assert that the caller account is new - this way, payout can only happen once + let callerAccountUpdate = AccountUpdate.create(callerAddress); + callerAccountUpdate.account.isNew.assertEquals(Bool(true)); + // pay out half of the zkapp balance to the caller + let balance = this.account.balance.get(); + this.account.balance.assertEquals(balance); + let halfBalance = balance.div(2); + this.send({ to: callerAccountUpdate, amount: halfBalance }); + + // emit some events + this.emitEvent('payoutReceiver', callerAddress); + this.emitEvent('payout', halfBalance); + } +} + +let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); +Mina.setActiveInstance(Local); + +// a test account that pays all the fees, and puts additional funds into the zkapp +let { privateKey: senderKey, publicKey: sender } = Local.testAccounts[0]; + +// the zkapp account +let zkappKey = PrivateKey.random(); +let zkappAddress = zkappKey.toPublicKey(); + +// a special account that is allowed to pull out half of the zkapp balance, once +let privilegedKey = PrivateKey.fromBase58( + 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' +); +let privilegedAddress = privilegedKey.toPublicKey(); + +let initialBalance = 10_000_000_000; +let initialState = Field(1); +let zkapp = new SimpleZkapp(zkappAddress); + +if (doProofs) { + console.log('compile'); + await SimpleZkapp.compile(); +} + +console.log('deploy'); +let tx = await Mina.transaction(sender, () => { + let senderUpdate = AccountUpdate.fundNewAccount(sender); + senderUpdate.send({ to: zkappAddress, amount: initialBalance }); + zkapp.deploy({ zkappKey }); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); + +console.log('initial state: ' + zkapp.x.get()); +console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); + +let account = Mina.getAccount(zkappAddress); +console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); + +console.log('update'); +tx = await Mina.transaction(sender, () => { + zkapp.update(Field(3)); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); + +// pay more into the zkapp -- this doesn't need a proof +console.log('receive'); +tx = await Mina.transaction(sender, () => { + let payerAccountUpdate = AccountUpdate.createSigned(sender); + payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); +}); +await tx.sign([senderKey]).send(); + +console.log('payout'); +tx = await Mina.transaction(sender, () => { + AccountUpdate.fundNewAccount(sender); + zkapp.payout(privilegedKey); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); +sender; + +console.log('final state: ' + zkapp.x.get()); +console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); From a77d0a801ea8c4a6825dd82487ccc6df43f95f2d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 18 Oct 2023 09:27:44 -0700 Subject: [PATCH 0262/1215] refactor(rot.ts): simplify witnessSlices function --- src/lib/gadgets/rot.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index c1763d1cbb..c1b45bcd0c 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -96,21 +96,12 @@ function rotate( return [rotated, excess, shifted]; } -function witnessSlices(f: Field, start: number, stop = -1) { - if (stop !== -1 && stop <= start) { - throw Error('stop must be greater than start'); - } +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); return Provable.witness(Field, () => { - let bits = f.toBits(); - if (stop > bits.length) { - throw Error('stop must be less than bit-length'); - } - if (stop === -1) { - stop = bits.length; - } - - return Field.fromBits(bits.slice(start, stop)); + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); }); } From fdc1061e0aac86d6af81d592023f051a70b52c4e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 18 Oct 2023 09:38:38 -0700 Subject: [PATCH 0263/1215] Revert "refactor(rot.ts): simplify witnessSlices function" This reverts commit a77d0a801ea8c4a6825dd82487ccc6df43f95f2d. --- src/lib/gadgets/rot.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index c1b45bcd0c..c1763d1cbb 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -96,12 +96,21 @@ function rotate( return [rotated, excess, shifted]; } -function witnessSlices(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); +function witnessSlices(f: Field, start: number, stop = -1) { + if (stop !== -1 && stop <= start) { + throw Error('stop must be greater than start'); + } return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); + let bits = f.toBits(); + if (stop > bits.length) { + throw Error('stop must be less than bit-length'); + } + if (stop === -1) { + stop = bits.length; + } + + return Field.fromBits(bits.slice(start, stop)); }); } From 0540b6718c6448383c9286767729df31ea70921e Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 19:00:27 +0200 Subject: [PATCH 0264/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index d4d7b792ee..83810ae030 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d4d7b792eeee6985e1efb8bb6cdd681e94b2bf86 +Subproject commit 83810ae030f0a57a2df9fd0a90a4911b32bcb34b From 82c89e5d4f45f815765ec6c253c4e44666ee24f2 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 18 Oct 2023 19:43:08 +0200 Subject: [PATCH 0265/1215] fix unit tests --- src/lib/gadgets/bitwise.unit-test.ts | 8 ++++++-- src/lib/gadgets/range-check.unit-test.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 12950672f9..4b92ce7aff 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -42,9 +42,13 @@ let maybeUint64: Spec = { }; // do a couple of proofs -equivalentAsync({ from: [maybeUint64, maybeUint64], to: field }, { runs: 3 })( +await equivalentAsync( + { from: [maybeUint64, maybeUint64], to: field }, + { runs: 3 } +)( (x, y) => { - if (x > 2 ** 64 || y > 2 ** 64) throw Error('Does not fit into 64 bits'); + if (x >= 2n ** 64n || y >= 2n ** 64n) + throw Error('Does not fit into 64 bits'); return Fp.xor(x, y); }, async (x, y) => { diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 13a44a059d..669f811174 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -35,7 +35,7 @@ let maybeUint64: Spec = { // do a couple of proofs // TODO: we use this as a test because there's no way to check custom gates quickly :( -equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); return true; From 9963735e0921659728a104ac394bcba03c0cba79 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 13:47:44 +0200 Subject: [PATCH 0266/1215] js storable interface --- src/lib/ml/base.ts | 4 ++-- src/lib/proof_system.ts | 19 +++++++++++++------ src/lib/storable.ts | 42 +++++++++++++++++++++++++++++++++++++++++ src/lib/util/fs.ts | 2 ++ src/lib/util/fs.web.ts | 5 +++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/lib/storable.ts create mode 100644 src/lib/util/fs.ts create mode 100644 src/lib/util/fs.web.ts diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 128fe0a9e1..0d6be322e8 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -88,10 +88,10 @@ const MlOption = Object.assign( ); const MlResult = { - return(t: T): MlResult { + ok(t: T): MlResult { return [0, t]; }, - unitError(): MlResult { + unitError(): MlResult { return [1, 0]; }, }; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 952f2fe2b9..b16b6577e9 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -28,6 +28,7 @@ import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlTuple } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; +import { Storable } from './storable.js'; // public API export { @@ -517,6 +518,7 @@ async function compileProgram({ methods, gates, proofSystemTag, + storable: { read, write } = Storable.FileSystemDefault, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -525,6 +527,7 @@ async function compileProgram({ methods: ((...args: any) => void)[]; gates: Gate[][]; proofSystemTag: { name: string }; + storable?: Storable; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => @@ -542,13 +545,17 @@ async function compileProgram({ let storable: Pickles.Storable = [ 0, - function read(key, path) { - console.log('READ', path); - return MlResult.unitError(); + function read_(_key, path) { + let value = read(path); + if (value === undefined) return MlResult.unitError(); + // TODO decode value + return MlResult.ok(value); }, - function write(key, path, value) { - console.log('WRITE', path, value); - return MlResult.unitError(); + function write_(_key, value, path) { + // TODO encode value + let stringValue = value; + if (write(path, stringValue)) return MlResult.ok(undefined); + return MlResult.unitError(); }, MlBool(true), ]; diff --git a/src/lib/storable.ts b/src/lib/storable.ts new file mode 100644 index 0000000000..d112257d5b --- /dev/null +++ b/src/lib/storable.ts @@ -0,0 +1,42 @@ +import { writeFileSync, readFileSync, mkdirSync } from './util/fs.js'; +import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; + +export { Storable }; + +type Storable = { + write(key: string, value: T): boolean; + read(key: string): T | undefined; +}; + +const FileSystem = (cacheDirectory: string): Storable => ({ + read(key) { + if (jsEnvironment !== 'node') return undefined; + console.log('READ', key); + try { + let buffer = readFileSync(`${cacheDirectory}/${key}`); + return new Uint8Array(buffer.buffer); + } catch (e: any) { + console.log('read failed', e.message); + return undefined; + } + }, + write(key, value) { + if (jsEnvironment !== 'node') return false; + console.log('WRITE', key); + try { + mkdirSync(cacheDirectory, { recursive: true }); + writeFileSync(`${cacheDirectory}/${key}`, value); + return true; + } catch (e: any) { + console.log('write failed', e.message); + return false; + } + }, +}); + +const FileSystemDefault = FileSystem('/tmp'); + +const Storable = { + FileSystem, + FileSystemDefault, +}; diff --git a/src/lib/util/fs.ts b/src/lib/util/fs.ts new file mode 100644 index 0000000000..e4e61589f5 --- /dev/null +++ b/src/lib/util/fs.ts @@ -0,0 +1,2 @@ +export { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; +export { resolve } from 'node:path'; diff --git a/src/lib/util/fs.web.ts b/src/lib/util/fs.web.ts new file mode 100644 index 0000000000..d394064aa1 --- /dev/null +++ b/src/lib/util/fs.web.ts @@ -0,0 +1,5 @@ +export { dummy as writeFileSync, dummy as readFileSync, dummy as resolve }; + +function dummy() { + throw Error('not implemented'); +} From 91f7d37e83bec686f3f6f8edbda75469a0b1bef1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 14:56:25 +0200 Subject: [PATCH 0267/1215] stub out key encoding functions --- src/lib/ml/base.ts | 12 ++++++- src/lib/proof-system/prover-keys.ts | 54 +++++++++++++++++++++++++++++ src/lib/proof_system.ts | 24 +++++++------ src/lib/util/fs.web.ts | 7 +++- src/snarky.d.ts | 13 +++++-- 5 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 src/lib/proof-system/prover-keys.ts diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 0d6be322e8..10243efab7 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -1,7 +1,16 @@ /** * This module contains basic methods for interacting with OCaml */ -export { MlArray, MlTuple, MlList, MlOption, MlBool, MlBytes, MlResult }; +export { + MlArray, + MlTuple, + MlList, + MlOption, + MlBool, + MlBytes, + MlResult, + MlUnit, +}; // ocaml types @@ -11,6 +20,7 @@ type MlList = [0, T, 0 | MlList]; type MlOption = 0 | [0, T]; type MlBool = 0 | 1; type MlResult = [0, T] | [1, E]; +type MlUnit = 0; /** * js_of_ocaml representation of a byte array, diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts new file mode 100644 index 0000000000..bed9ac51fa --- /dev/null +++ b/src/lib/proof-system/prover-keys.ts @@ -0,0 +1,54 @@ +import { + WasmPastaFpPlonkIndex, + WasmPastaFqPlonkIndex, +} from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; + +export { encodeProverKey, decodeProverKey, AnyKey, AnyValue }; + +type TODO = any; +type Opaque = unknown; + +type MlConstraintSystem = Opaque; // opaque + +// Dlog_plonk_based_keypair.Make().t + +type MlBackendKeyPair = [ + _: 0, + index: WasmIndex, + cs: MlConstraintSystem +]; + +// pickles_bindings.ml, any_key enum + +enum KeyType { + StepProverKey, + StepVerifierKey, + WrapProverKey, + WrapVerifierKey, +} + +// TODO better names + +type AnyKey = + | [KeyType.StepProverKey, TODO] + | [KeyType.StepVerifierKey, TODO] + | [KeyType.WrapProverKey, TODO] + | [KeyType.WrapVerifierKey, TODO]; + +type AnyValue = + | [KeyType.StepProverKey, MlBackendKeyPair] + | [KeyType.StepVerifierKey, TODO] + | [KeyType.WrapProverKey, MlBackendKeyPair] + | [KeyType.WrapVerifierKey, TODO]; + +function encodeProverKey(value: AnyValue): Uint8Array { + console.log('ENCODE', value); + // TODO + return value as any; +} + +function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { + console.log('DECODE', key); + // TODO + return bytes as any; +} diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index b16b6577e9..9dac9af708 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -25,10 +25,14 @@ import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; -import { MlArray, MlBool, MlResult, MlTuple } from './ml/base.js'; +import { MlArray, MlBool, MlResult, MlTuple, MlUnit } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; import { Storable } from './storable.js'; +import { + decodeProverKey, + encodeProverKey, +} from './proof-system/prover-keys.js'; // public API export { @@ -545,17 +549,15 @@ async function compileProgram({ let storable: Pickles.Storable = [ 0, - function read_(_key, path) { - let value = read(path); - if (value === undefined) return MlResult.unitError(); - // TODO decode value - return MlResult.ok(value); + function read_(key, path) { + let bytes = read(path); + if (bytes === undefined) return MlResult.unitError(); + return MlResult.ok(decodeProverKey(key, bytes)); }, - function write_(_key, value, path) { - // TODO encode value - let stringValue = value; - if (write(path, stringValue)) return MlResult.ok(undefined); - return MlResult.unitError(); + function write_(key, value, path) { + let bytes = encodeProverKey(value); + if (write(path, bytes)) return MlResult.ok(undefined); + return MlResult.unitError(); }, MlBool(true), ]; diff --git a/src/lib/util/fs.web.ts b/src/lib/util/fs.web.ts index d394064aa1..1cc5bd4c8f 100644 --- a/src/lib/util/fs.web.ts +++ b/src/lib/util/fs.web.ts @@ -1,4 +1,9 @@ -export { dummy as writeFileSync, dummy as readFileSync, dummy as resolve }; +export { + dummy as writeFileSync, + dummy as readFileSync, + dummy as resolve, + dummy as mkdirSync, +}; function dummy() { throw Error('not implemented'); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 0967ff7ec4..ff2f3b41b5 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -10,8 +10,10 @@ import type { MlBool, MlBytes, MlResult, + MlUnit, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; +import type * as ProverKeys from './lib/proof-system/prover-keys.ts'; export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType }; @@ -602,8 +604,15 @@ declare namespace Pickles { */ type Storable = [ _: 0, - read: (key: any, path: string) => MlResult, - write: (key: any, value: any, path: string) => MlResult, + read: ( + key: ProverKeys.AnyKey, + path: string + ) => MlResult, + write: ( + key: ProverKeys.AnyKey, + value: ProverKeys.AnyValue, + path: string + ) => MlResult, canWrite: MlBool ]; From ab1f975b482989f6e652608c39df824a347b136b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 15:48:42 +0200 Subject: [PATCH 0268/1215] tweak error logic so that encoding/decoding can fail --- src/lib/proof-system/prover-keys.ts | 6 ++---- src/lib/proof_system.ts | 21 +++++++++++++------ src/lib/storable.ts | 31 +++++++++++------------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index bed9ac51fa..c8bee1a720 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -43,12 +43,10 @@ type AnyValue = function encodeProverKey(value: AnyValue): Uint8Array { console.log('ENCODE', value); - // TODO - return value as any; + throw Error('todo'); } function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { console.log('DECODE', key); - // TODO - return bytes as any; + throw Error('todo'); } diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 9dac9af708..40cc1a082d 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -550,14 +550,23 @@ async function compileProgram({ let storable: Pickles.Storable = [ 0, function read_(key, path) { - let bytes = read(path); - if (bytes === undefined) return MlResult.unitError(); - return MlResult.ok(decodeProverKey(key, bytes)); + try { + let bytes = read(path); + return MlResult.ok(decodeProverKey(key, bytes)); + } catch (e: any) { + console.log('read failed', e.message); + return MlResult.unitError(); + } }, function write_(key, value, path) { - let bytes = encodeProverKey(value); - if (write(path, bytes)) return MlResult.ok(undefined); - return MlResult.unitError(); + try { + let bytes = encodeProverKey(value); + write(path, bytes); + return MlResult.ok(undefined); + } catch (e: any) { + console.log('write failed', e.message); + return MlResult.unitError(); + } }, MlBool(true), ]; diff --git a/src/lib/storable.ts b/src/lib/storable.ts index d112257d5b..7e573a0ad5 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -3,34 +3,27 @@ import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; export { Storable }; +/** + * Interface for storing and retrieving values for caching. + * `read()` and `write()` can just throw errors on failure. + */ type Storable = { - write(key: string, value: T): boolean; - read(key: string): T | undefined; + read(key: string): T; + write(key: string, value: T): void; }; const FileSystem = (cacheDirectory: string): Storable => ({ read(key) { - if (jsEnvironment !== 'node') return undefined; + if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('READ', key); - try { - let buffer = readFileSync(`${cacheDirectory}/${key}`); - return new Uint8Array(buffer.buffer); - } catch (e: any) { - console.log('read failed', e.message); - return undefined; - } + let buffer = readFileSync(`${cacheDirectory}/${key}`); + return new Uint8Array(buffer.buffer); }, write(key, value) { - if (jsEnvironment !== 'node') return false; + if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('WRITE', key); - try { - mkdirSync(cacheDirectory, { recursive: true }); - writeFileSync(`${cacheDirectory}/${key}`, value); - return true; - } catch (e: any) { - console.log('write failed', e.message); - return false; - } + mkdirSync(cacheDirectory, { recursive: true }); + writeFileSync(`${cacheDirectory}/${key}`, value); }, }); From 17d1a8dc24523d90363c854dd7f6555c782b0a87 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 15:59:11 +0200 Subject: [PATCH 0269/1215] get prover key storing to work --- src/lib/proof-system/prover-keys.ts | 30 +++++++++++++++++++++++++---- src/snarky.d.ts | 3 ++- src/snarky.js | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index c8bee1a720..2b8f97ea47 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -2,6 +2,7 @@ import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex, } from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; +import { getWasm } from '../../snarky.js'; export { encodeProverKey, decodeProverKey, AnyKey, AnyValue }; @@ -37,16 +38,37 @@ type AnyKey = type AnyValue = | [KeyType.StepProverKey, MlBackendKeyPair] - | [KeyType.StepVerifierKey, TODO] + | [KeyType.StepVerifierKey, unknown] | [KeyType.WrapProverKey, MlBackendKeyPair] - | [KeyType.WrapVerifierKey, TODO]; + | [KeyType.WrapVerifierKey, unknown]; function encodeProverKey(value: AnyValue): Uint8Array { console.log('ENCODE', value); - throw Error('todo'); + let wasm = getWasm(); + switch (value[0]) { + case KeyType.StepProverKey: + return wasm.caml_pasta_fp_plonk_index_encode(value[1][1]); + default: + throw Error('todo'); + } } function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { console.log('DECODE', key); - throw Error('todo'); + let wasm = getWasm(); + switch (key[0]) { + case KeyType.StepProverKey: + throw Error('todo'); + // return [ + // KeyType.StepProverKey, + // [ + // 0, + // wasm.caml_pasta_fp_plonk_index_decode(bytes), + // // TODO + // null, + // ], + // ]; + default: + throw Error('todo'); + } } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index ff2f3b41b5..ffdceec54e 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -14,8 +14,9 @@ import type { } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; import type * as ProverKeys from './lib/proof-system/prover-keys.ts'; +import { getWasm } from './bindings/js/wrapper.js'; -export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType }; +export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType, getWasm }; // internal export { diff --git a/src/snarky.js b/src/snarky.js index 698a4a17b6..b80fa8e6bd 100644 --- a/src/snarky.js +++ b/src/snarky.js @@ -1,9 +1,9 @@ import './bindings/crypto/bindings.js'; -import { getSnarky, withThreadPool } from './bindings/js/wrapper.js'; +import { getSnarky, getWasm, withThreadPool } from './bindings/js/wrapper.js'; import snarkySpec from './bindings/js/snarky-class-spec.js'; import { proxyClasses } from './bindings/js/proxy.js'; -export { Snarky, Ledger, Pickles, Test, withThreadPool }; +export { Snarky, Ledger, Pickles, Test, withThreadPool, getWasm }; let isReadyBoolean = true; let isItReady = () => isReadyBoolean; From 7817ddefa07856ddc242faa3754e9d858ba478b3 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 16:38:27 +0200 Subject: [PATCH 0270/1215] fix prover error and dump vks --- src/examples/regression_test.json | 4 +- src/lib/gadgets/bitwise.ts | 175 ++++++++++++++++++++---------- 2 files changed, 121 insertions(+), 58 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 817796fa3a..d3d278bf2e 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -169,8 +169,8 @@ "digest": "Bitwise Primitive", "methods": { "xor": { - "rows": 15, - "digest": "b3595a9cc9562d4f4a3a397b6de44971" + "rows": 75, + "digest": "44c171b0efd13d6380e470965919b663" } }, "verificationKey": { diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index fc284bdcae..8a342cdfa5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,8 +5,19 @@ import * as Gates from '../gates.js'; export { xor }; +// XOR specific constants const LENGTH_XOR = 4; +const twoPowLen = new Field(2 ** LENGTH_XOR); +const twoPow2Len = twoPowLen.mul(twoPowLen); +const twoPow3Len = twoPow2Len.mul(twoPowLen); +const twoPow4Len = twoPow3Len.mul(twoPowLen); + +// 4 bit sized offsets +const firstOffset = LENGTH_XOR; +const secondOffset = firstOffset + LENGTH_XOR; +const thirdOffset = secondOffset + LENGTH_XOR; + function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -56,82 +67,134 @@ function buildXor( expectedOutput: Field, padLength: number ) { - // 4 bit sized offsets - let first = LENGTH_XOR; - let second = first + LENGTH_XOR; - let third = second + LENGTH_XOR; + let iterations = Math.ceil(padLength / (4 * LENGTH_XOR)); + + // pre compute all inputs for the xor chain + let precomputedInputs = computeChainInputs(iterations, a, b, expectedOutput); + + precomputedInputs.forEach( + ( + { + a, + b, + expectedOutput, + in1: [in1_0, in1_1, in1_2, in1_3], + in2: [in2_0, in2_1, in2_2, in2_3], + out: [out0, out1, out2, out3], + }, + n + ) => { + // assert that xor of the slices is correct, 16 bit at a time + Gates.xor( + a, + b, + expectedOutput, + in1_0, + in1_1, + in1_2, + in1_3, + in2_0, + in2_1, + in2_2, + in2_3, + out0, + out1, + out2, + out3 + ); + + // if we reached the end of our chain, assert that the inputs are zero and add a zero gate + if (n === iterations - 1) { + Gates.zero(a, b, expectedOutput); + + let zero = new Field(0); + zero.assertEquals(a); + zero.assertEquals(b); + zero.assertEquals(expectedOutput); + } + } + ); +} + +function assert(stmt: boolean, message?: string) { + if (!stmt) { + throw Error(message ?? 'Assertion failed'); + } +} + +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); - // construct the chain of XORs until padLength is 0 - while (padLength !== 0) { + return Provable.witness(Field, () => { + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); + }); +} + +function computeChainInputs( + iterations: number, + a: Field, + b: Field, + expectedOutput: Field +) { + let precomputedVariables = []; + + for (let i = 0; i < iterations; i++) { // slices the inputs into LENGTH_XOR-sized chunks // slices of a let in1_0 = witnessSlices(a, 0, LENGTH_XOR); - let in1_1 = witnessSlices(a, first, LENGTH_XOR); - let in1_2 = witnessSlices(a, second, LENGTH_XOR); - let in1_3 = witnessSlices(a, third, LENGTH_XOR); + let in1_1 = witnessSlices(a, firstOffset, LENGTH_XOR); + let in1_2 = witnessSlices(a, secondOffset, LENGTH_XOR); + let in1_3 = witnessSlices(a, thirdOffset, LENGTH_XOR); // slices of b let in2_0 = witnessSlices(b, 0, LENGTH_XOR); - let in2_1 = witnessSlices(b, first, LENGTH_XOR); - let in2_2 = witnessSlices(b, second, LENGTH_XOR); - let in2_3 = witnessSlices(b, third, LENGTH_XOR); + let in2_1 = witnessSlices(b, firstOffset, LENGTH_XOR); + let in2_2 = witnessSlices(b, secondOffset, LENGTH_XOR); + let in2_3 = witnessSlices(b, thirdOffset, LENGTH_XOR); // slice of expected output let out0 = witnessSlices(expectedOutput, 0, LENGTH_XOR); - let out1 = witnessSlices(expectedOutput, first, LENGTH_XOR); - let out2 = witnessSlices(expectedOutput, second, LENGTH_XOR); - let out3 = witnessSlices(expectedOutput, third, LENGTH_XOR); + let out1 = witnessSlices(expectedOutput, firstOffset, LENGTH_XOR); + let out2 = witnessSlices(expectedOutput, secondOffset, LENGTH_XOR); + let out3 = witnessSlices(expectedOutput, thirdOffset, LENGTH_XOR); - // assert that the xor of the slices is correct, 16 bit at a time - Gates.xor( + // store all inputs for the current iteration of the XOR chain + precomputedVariables[i] = { a, b, expectedOutput, - in1_0, - in1_1, - in1_2, - in1_3, - in2_0, - in2_1, - in2_2, - in2_3, + in1: [in1_0, in1_1, in1_2, in1_3], + in2: [in2_0, in2_1, in2_2, in2_3], + out: [out0, out1, out2, out3], + }; + + // update the values for the next loop iteration - seal them to force snarky to compute the constraints directly + a = calculateNextValue(a, in1_0, in1_1, in1_2, in1_3).seal(); + b = calculateNextValue(b, in2_0, in2_1, in2_2, in2_3).seal(); + expectedOutput = calculateNextValue( + expectedOutput, out0, out1, out2, out3 - ); - - // update the values for the next loop iteration - a = witnessNextValue(a); - b = witnessNextValue(b); - expectedOutput = witnessNextValue(expectedOutput); - padLength = padLength - 4 * LENGTH_XOR; + ).seal(); } - // inputs are zero and length is zero, add the zero check - we reached the end of our chain - Gates.zero(a, b, expectedOutput); - - let zero = new Field(0); - zero.assertEquals(a); - zero.assertEquals(b); - zero.assertEquals(expectedOutput); -} - -function assert(stmt: boolean, message?: string) { - if (!stmt) { - throw Error(message ?? 'Assertion failed'); - } + return precomputedVariables; } -function witnessSlices(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - - return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); - }); -} - -function witnessNextValue(current: Field) { - return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); +function calculateNextValue( + current: Field, + var0: Field, + var1: Field, + var2: Field, + var3: Field +) { + return current + .sub(var0) + .sub(var1.mul(twoPowLen)) + .sub(var2.mul(twoPow2Len)) + .sub(var3.mul(twoPow3Len)) + .div(twoPow4Len); } From 71212604cbec8b3c386e38d77b509db6cd3dc701 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 16:41:40 +0200 Subject: [PATCH 0271/1215] first attempt at reading step pk --- src/lib/proof-system/prover-keys.ts | 61 ++++++++++++++++------------- src/snarky.d.ts | 9 ++++- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 2b8f97ea47..edea0fcdc6 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -2,14 +2,17 @@ import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex, } from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; -import { getWasm } from '../../snarky.js'; +import { Pickles, getWasm } from '../../snarky.js'; export { encodeProverKey, decodeProverKey, AnyKey, AnyValue }; -type TODO = any; +type TODO = unknown; type Opaque = unknown; -type MlConstraintSystem = Opaque; // opaque +// Plonk_constraint_system.Make()().t +class MlConstraintSystem { + // opaque type +} // Dlog_plonk_based_keypair.Make().t @@ -19,34 +22,44 @@ type MlBackendKeyPair = [ cs: MlConstraintSystem ]; +// Pickles.Cache.{Step,Wrap}.Key.Proving.t + +type MlProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: Opaque, + index: number, + constraintSystem: MlConstraintSystem +]; + // pickles_bindings.ml, any_key enum enum KeyType { - StepProverKey, - StepVerifierKey, - WrapProverKey, - WrapVerifierKey, + StepProvingKey, + StepVerificationKey, + WrapProvingKey, + WrapVerificationKey, } // TODO better names type AnyKey = - | [KeyType.StepProverKey, TODO] - | [KeyType.StepVerifierKey, TODO] - | [KeyType.WrapProverKey, TODO] - | [KeyType.WrapVerifierKey, TODO]; + | [KeyType.StepProvingKey, MlProvingKeyHeader] + | [KeyType.StepVerificationKey, TODO] + | [KeyType.WrapProvingKey, MlProvingKeyHeader] + | [KeyType.WrapVerificationKey, TODO]; type AnyValue = - | [KeyType.StepProverKey, MlBackendKeyPair] - | [KeyType.StepVerifierKey, unknown] - | [KeyType.WrapProverKey, MlBackendKeyPair] - | [KeyType.WrapVerifierKey, unknown]; + | [KeyType.StepProvingKey, MlBackendKeyPair] + | [KeyType.StepVerificationKey, TODO] + | [KeyType.WrapProvingKey, MlBackendKeyPair] + | [KeyType.WrapVerificationKey, TODO]; function encodeProverKey(value: AnyValue): Uint8Array { console.log('ENCODE', value); let wasm = getWasm(); switch (value[0]) { - case KeyType.StepProverKey: + case KeyType.StepProvingKey: return wasm.caml_pasta_fp_plonk_index_encode(value[1][1]); default: throw Error('todo'); @@ -57,17 +70,11 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { console.log('DECODE', key); let wasm = getWasm(); switch (key[0]) { - case KeyType.StepProverKey: - throw Error('todo'); - // return [ - // KeyType.StepProverKey, - // [ - // 0, - // wasm.caml_pasta_fp_plonk_index_decode(bytes), - // // TODO - // null, - // ], - // ]; + case KeyType.StepProvingKey: + let srs = Pickles.loadSrsFp(); + let index = wasm.caml_pasta_fp_plonk_index_decode(bytes, srs); + let cs = key[1][4]; + return [KeyType.StepProvingKey, [0, index, cs]]; default: throw Error('todo'); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index ffdceec54e..9bd2a1d051 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -13,8 +13,12 @@ import type { MlUnit, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; -import type * as ProverKeys from './lib/proof-system/prover-keys.ts'; +import type * as ProverKeys from './lib/proof-system/prover-keys.js'; import { getWasm } from './bindings/js/wrapper.js'; +import type { + WasmFpSrs, + WasmFqSrs, +} from './bindings/compiled/node_bindings/plonk_wasm.cjs'; export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType, getWasm }; @@ -672,6 +676,9 @@ declare const Pickles: { verificationKey: string ): Promise; + loadSrsFp(): WasmFpSrs; + loadSrsFq(): WasmFqSrs; + dummyBase64Proof: () => string; /** * @returns (base64 vk, hash) From aa9b9aee8d422572618c9ce8ef8c4d1ab802d34a Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 17:05:05 +0200 Subject: [PATCH 0272/1215] fix chain end values --- src/lib/gadgets/bitwise.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 8a342cdfa5..299c250110 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -70,9 +70,10 @@ function buildXor( let iterations = Math.ceil(padLength / (4 * LENGTH_XOR)); // pre compute all inputs for the xor chain - let precomputedInputs = computeChainInputs(iterations, a, b, expectedOutput); + let precomputed = computeChainInputs(iterations, a, b, expectedOutput); - precomputedInputs.forEach( + // for each iteration of the xor chain, take the precomputed inputs and assert that the xor operation of the slices is correct + precomputed.inputs.forEach( ( { a, @@ -103,14 +104,16 @@ function buildXor( out3 ); - // if we reached the end of our chain, assert that the inputs are zero and add a zero gate + // if we reached the end of our chain, assert that the final inputs are zero and add a zero gate if (n === iterations - 1) { - Gates.zero(a, b, expectedOutput); + // final values for a, b and output - the end of the XOR chain - which should be zero + let { finalA, finalB, finalOutput } = precomputed; + Gates.zero(finalA, finalB, finalOutput); let zero = new Field(0); - zero.assertEquals(a); - zero.assertEquals(b); - zero.assertEquals(expectedOutput); + zero.assertEquals(finalA); + zero.assertEquals(finalB); + zero.assertEquals(finalOutput); } } ); @@ -137,7 +140,7 @@ function computeChainInputs( b: Field, expectedOutput: Field ) { - let precomputedVariables = []; + let inputs = []; for (let i = 0; i < iterations; i++) { // slices the inputs into LENGTH_XOR-sized chunks @@ -160,7 +163,7 @@ function computeChainInputs( let out3 = witnessSlices(expectedOutput, thirdOffset, LENGTH_XOR); // store all inputs for the current iteration of the XOR chain - precomputedVariables[i] = { + inputs[i] = { a, b, expectedOutput, @@ -181,7 +184,8 @@ function computeChainInputs( ).seal(); } - return precomputedVariables; + // we return the precomputed inputs to the XOR chain and the final values of a, b and output for the final zero gate and zero assertion check + return { inputs, finalA: a, finalB: b, finalOutput: expectedOutput }; } function calculateNextValue( From 1f1df6bd72b9f58477552397417c68dd92d6849d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 19:00:15 +0200 Subject: [PATCH 0273/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 851d3df238..70358c321b 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 851d3df2385c693bd4f652ad7a0a1af98491e99c +Subproject commit 70358c321b18988b81d89bbc47f9ea55459da056 From b5b34b0b61347dd51400e43a94d52866be645465 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 19:16:37 +0200 Subject: [PATCH 0274/1215] revert precalculation of inputs --- src/lib/gadgets/bitwise.ts | 191 ++++++++++++------------------------- 1 file changed, 61 insertions(+), 130 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 299c250110..32c54ef085 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,19 +5,6 @@ import * as Gates from '../gates.js'; export { xor }; -// XOR specific constants -const LENGTH_XOR = 4; - -const twoPowLen = new Field(2 ** LENGTH_XOR); -const twoPow2Len = twoPowLen.mul(twoPowLen); -const twoPow3Len = twoPow2Len.mul(twoPowLen); -const twoPow4Len = twoPow3Len.mul(twoPowLen); - -// 4 bit sized offsets -const firstOffset = LENGTH_XOR; -const secondOffset = firstOffset + LENGTH_XOR; -const thirdOffset = secondOffset + LENGTH_XOR; - function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -49,8 +36,8 @@ function xor(a: Field, b: Field, length: number) { () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) ); - // obtain pad length until the length is a multiple of 4*n for n-bit length lookup table - let l = 4 * LENGTH_XOR; + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let l = 16; let padLength = Math.ceil(length / l) * l; // recursively build xor gadget chain @@ -67,138 +54,82 @@ function buildXor( expectedOutput: Field, padLength: number ) { - let iterations = Math.ceil(padLength / (4 * LENGTH_XOR)); - - // pre compute all inputs for the xor chain - let precomputed = computeChainInputs(iterations, a, b, expectedOutput); - - // for each iteration of the xor chain, take the precomputed inputs and assert that the xor operation of the slices is correct - precomputed.inputs.forEach( - ( - { - a, - b, - expectedOutput, - in1: [in1_0, in1_1, in1_2, in1_3], - in2: [in2_0, in2_1, in2_2, in2_3], - out: [out0, out1, out2, out3], - }, - n - ) => { - // assert that xor of the slices is correct, 16 bit at a time - Gates.xor( - a, - b, - expectedOutput, - in1_0, - in1_1, - in1_2, - in1_3, - in2_0, - in2_1, - in2_2, - in2_3, - out0, - out1, - out2, - out3 - ); - - // if we reached the end of our chain, assert that the final inputs are zero and add a zero gate - if (n === iterations - 1) { - // final values for a, b and output - the end of the XOR chain - which should be zero - let { finalA, finalB, finalOutput } = precomputed; - - Gates.zero(finalA, finalB, finalOutput); - let zero = new Field(0); - zero.assertEquals(finalA); - zero.assertEquals(finalB); - zero.assertEquals(finalOutput); - } - } - ); -} - -function assert(stmt: boolean, message?: string) { - if (!stmt) { - throw Error(message ?? 'Assertion failed'); - } -} - -function witnessSlices(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - - return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); - }); -} - -function computeChainInputs( - iterations: number, - a: Field, - b: Field, - expectedOutput: Field -) { - let inputs = []; + // 4 bit sized offsets + let first = 4; + let second = first + 4; + let third = second + 4; - for (let i = 0; i < iterations; i++) { + // construct the chain of XORs until padLength is 0 + while (padLength !== 0) { // slices the inputs into LENGTH_XOR-sized chunks // slices of a - let in1_0 = witnessSlices(a, 0, LENGTH_XOR); - let in1_1 = witnessSlices(a, firstOffset, LENGTH_XOR); - let in1_2 = witnessSlices(a, secondOffset, LENGTH_XOR); - let in1_3 = witnessSlices(a, thirdOffset, LENGTH_XOR); + let in1_0 = witnessSlices(a, 0, 4); + let in1_1 = witnessSlices(a, first, 4); + let in1_2 = witnessSlices(a, second, 4); + let in1_3 = witnessSlices(a, third, 4); // slices of b - let in2_0 = witnessSlices(b, 0, LENGTH_XOR); - let in2_1 = witnessSlices(b, firstOffset, LENGTH_XOR); - let in2_2 = witnessSlices(b, secondOffset, LENGTH_XOR); - let in2_3 = witnessSlices(b, thirdOffset, LENGTH_XOR); + let in2_0 = witnessSlices(b, 0, 4); + let in2_1 = witnessSlices(b, first, 4); + let in2_2 = witnessSlices(b, second, 4); + let in2_3 = witnessSlices(b, third, 4); // slice of expected output - let out0 = witnessSlices(expectedOutput, 0, LENGTH_XOR); - let out1 = witnessSlices(expectedOutput, firstOffset, LENGTH_XOR); - let out2 = witnessSlices(expectedOutput, secondOffset, LENGTH_XOR); - let out3 = witnessSlices(expectedOutput, thirdOffset, LENGTH_XOR); + let out0 = witnessSlices(expectedOutput, 0, 4); + let out1 = witnessSlices(expectedOutput, first, 4); + let out2 = witnessSlices(expectedOutput, second, 4); + let out3 = witnessSlices(expectedOutput, third, 4); - // store all inputs for the current iteration of the XOR chain - inputs[i] = { + // assert that the xor of the slices is correct, 16 bit at a time + Gates.xor( a, b, expectedOutput, - in1: [in1_0, in1_1, in1_2, in1_3], - in2: [in2_0, in2_1, in2_2, in2_3], - out: [out0, out1, out2, out3], - }; - - // update the values for the next loop iteration - seal them to force snarky to compute the constraints directly - a = calculateNextValue(a, in1_0, in1_1, in1_2, in1_3).seal(); - b = calculateNextValue(b, in2_0, in2_1, in2_2, in2_3).seal(); - expectedOutput = calculateNextValue( - expectedOutput, + in1_0, + in1_1, + in1_2, + in1_3, + in2_0, + in2_1, + in2_2, + in2_3, out0, out1, out2, out3 - ).seal(); + ); + + // update the values for the next loop iteration + a = witnessNextValue(a); + b = witnessNextValue(b); + expectedOutput = witnessNextValue(expectedOutput); + padLength = padLength - 16; } - // we return the precomputed inputs to the XOR chain and the final values of a, b and output for the final zero gate and zero assertion check - return { inputs, finalA: a, finalB: b, finalOutput: expectedOutput }; + // inputs are zero and length is zero, add the zero check - we reached the end of our chain + Gates.zero(a, b, expectedOutput); + + let zero = new Field(0); + zero.assertEquals(a); + zero.assertEquals(b); + zero.assertEquals(expectedOutput); } -function calculateNextValue( - current: Field, - var0: Field, - var1: Field, - var2: Field, - var3: Field -) { - return current - .sub(var0) - .sub(var1.mul(twoPowLen)) - .sub(var2.mul(twoPow2Len)) - .sub(var3.mul(twoPow3Len)) - .div(twoPow4Len); +function assert(stmt: boolean, message?: string) { + if (!stmt) { + throw Error(message ?? 'Assertion failed'); + } +} + +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); + + return Provable.witness(Field, () => { + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); + }); +} + +function witnessNextValue(current: Field) { + return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); } From e1c22af7fb05f8cf01e05b3ffa9c3dd89e7627a2 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 19:19:20 +0200 Subject: [PATCH 0275/1215] dump vks --- src/examples/regression_test.json | 2 +- src/lib/gadgets/bitwise.ts | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index d3d278bf2e..3d4409de2d 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -170,7 +170,7 @@ "methods": { "xor": { "rows": 75, - "digest": "44c171b0efd13d6380e470965919b663" + "digest": "8d87d6130e66394180b88c76c11a4baf" } }, "verificationKey": { diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 32c54ef085..9604be32ef 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,6 +5,11 @@ import * as Gates from '../gates.js'; export { xor }; +// 4 bit sized offsets +const firstChunk = 4; +const secondChunk = firstChunk + 4; +const thirdChunk = secondChunk + 4; + function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -54,31 +59,26 @@ function buildXor( expectedOutput: Field, padLength: number ) { - // 4 bit sized offsets - let first = 4; - let second = first + 4; - let third = second + 4; - // construct the chain of XORs until padLength is 0 while (padLength !== 0) { - // slices the inputs into LENGTH_XOR-sized chunks + // slices the inputs 4bit-sized chunks // slices of a let in1_0 = witnessSlices(a, 0, 4); - let in1_1 = witnessSlices(a, first, 4); - let in1_2 = witnessSlices(a, second, 4); - let in1_3 = witnessSlices(a, third, 4); + let in1_1 = witnessSlices(a, firstChunk, 4); + let in1_2 = witnessSlices(a, secondChunk, 4); + let in1_3 = witnessSlices(a, thirdChunk, 4); // slices of b let in2_0 = witnessSlices(b, 0, 4); - let in2_1 = witnessSlices(b, first, 4); - let in2_2 = witnessSlices(b, second, 4); - let in2_3 = witnessSlices(b, third, 4); + let in2_1 = witnessSlices(b, firstChunk, 4); + let in2_2 = witnessSlices(b, secondChunk, 4); + let in2_3 = witnessSlices(b, thirdChunk, 4); // slice of expected output let out0 = witnessSlices(expectedOutput, 0, 4); - let out1 = witnessSlices(expectedOutput, first, 4); - let out2 = witnessSlices(expectedOutput, second, 4); - let out3 = witnessSlices(expectedOutput, third, 4); + let out1 = witnessSlices(expectedOutput, firstChunk, 4); + let out2 = witnessSlices(expectedOutput, secondChunk, 4); + let out3 = witnessSlices(expectedOutput, thirdChunk, 4); // assert that the xor of the slices is correct, 16 bit at a time Gates.xor( From eb8143b107c554780fc07490d3ef78ee16b805ce Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 19:21:59 +0200 Subject: [PATCH 0276/1215] move constants --- src/lib/gadgets/bitwise.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 9604be32ef..bd218a5314 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,11 +5,6 @@ import * as Gates from '../gates.js'; export { xor }; -// 4 bit sized offsets -const firstChunk = 4; -const secondChunk = firstChunk + 4; -const thirdChunk = secondChunk + 4; - function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -59,6 +54,11 @@ function buildXor( expectedOutput: Field, padLength: number ) { + // 4 bit sized offsets + const firstChunk = 4; + const secondChunk = firstChunk + 4; + const thirdChunk = secondChunk + 4; + // construct the chain of XORs until padLength is 0 while (padLength !== 0) { // slices the inputs 4bit-sized chunks From 043f0ef0808de2c32d4a324204e55e1b2e3b5c74 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 19:24:24 +0200 Subject: [PATCH 0277/1215] dump regression test vks --- src/examples/regression_test.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 3d4409de2d..817796fa3a 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -169,8 +169,8 @@ "digest": "Bitwise Primitive", "methods": { "xor": { - "rows": 75, - "digest": "8d87d6130e66394180b88c76c11a4baf" + "rows": 15, + "digest": "b3595a9cc9562d4f4a3a397b6de44971" } }, "verificationKey": { From 36830a82eff1a757a2270178e9bb12506140abd2 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 19 Oct 2023 19:26:23 +0200 Subject: [PATCH 0278/1215] hardcode chunk size --- src/lib/gadgets/bitwise.ts | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index bd218a5314..8e2465d3d5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -54,31 +54,26 @@ function buildXor( expectedOutput: Field, padLength: number ) { - // 4 bit sized offsets - const firstChunk = 4; - const secondChunk = firstChunk + 4; - const thirdChunk = secondChunk + 4; - // construct the chain of XORs until padLength is 0 while (padLength !== 0) { - // slices the inputs 4bit-sized chunks + // slices the inputs 4 4bit-sized chunks // slices of a let in1_0 = witnessSlices(a, 0, 4); - let in1_1 = witnessSlices(a, firstChunk, 4); - let in1_2 = witnessSlices(a, secondChunk, 4); - let in1_3 = witnessSlices(a, thirdChunk, 4); + let in1_1 = witnessSlices(a, 4, 4); + let in1_2 = witnessSlices(a, 8, 4); + let in1_3 = witnessSlices(a, 12, 4); // slices of b let in2_0 = witnessSlices(b, 0, 4); - let in2_1 = witnessSlices(b, firstChunk, 4); - let in2_2 = witnessSlices(b, secondChunk, 4); - let in2_3 = witnessSlices(b, thirdChunk, 4); + let in2_1 = witnessSlices(b, 4, 4); + let in2_2 = witnessSlices(b, 8, 4); + let in2_3 = witnessSlices(b, 12, 4); // slice of expected output let out0 = witnessSlices(expectedOutput, 0, 4); - let out1 = witnessSlices(expectedOutput, firstChunk, 4); - let out2 = witnessSlices(expectedOutput, secondChunk, 4); - let out3 = witnessSlices(expectedOutput, thirdChunk, 4); + let out1 = witnessSlices(expectedOutput, 4, 4); + let out2 = witnessSlices(expectedOutput, 8, 4); + let out3 = witnessSlices(expectedOutput, 12, 4); // assert that the xor of the slices is correct, 16 bit at a time Gates.xor( From eeae4cfd5ab0d417b754d59f6a9d681b3c05f444 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 19 Oct 2023 20:58:01 +0200 Subject: [PATCH 0279/1215] wrap proving key --- src/lib/proof-system/prover-keys.ts | 44 +++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index edea0fcdc6..c6bb05fe73 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -24,7 +24,7 @@ type MlBackendKeyPair = [ // Pickles.Cache.{Step,Wrap}.Key.Proving.t -type MlProvingKeyHeader = [ +type MlStepProvingKeyHeader = [ _: 0, typeEqual: number, snarkKeysHeader: Opaque, @@ -32,6 +32,13 @@ type MlProvingKeyHeader = [ constraintSystem: MlConstraintSystem ]; +type MlWrapProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: Opaque, + constraintSystem: MlConstraintSystem +]; + // pickles_bindings.ml, any_key enum enum KeyType { @@ -44,9 +51,9 @@ enum KeyType { // TODO better names type AnyKey = - | [KeyType.StepProvingKey, MlProvingKeyHeader] + | [KeyType.StepProvingKey, MlStepProvingKeyHeader] | [KeyType.StepVerificationKey, TODO] - | [KeyType.WrapProvingKey, MlProvingKeyHeader] + | [KeyType.WrapProvingKey, MlWrapProvingKeyHeader] | [KeyType.WrapVerificationKey, TODO]; type AnyValue = @@ -56,25 +63,46 @@ type AnyValue = | [KeyType.WrapVerificationKey, TODO]; function encodeProverKey(value: AnyValue): Uint8Array { - console.log('ENCODE', value); let wasm = getWasm(); switch (value[0]) { - case KeyType.StepProvingKey: - return wasm.caml_pasta_fp_plonk_index_encode(value[1][1]); + case KeyType.StepProvingKey: { + let index = value[1][1]; + console.time('encode index'); + let encoded = wasm.caml_pasta_fp_plonk_index_encode(index); + console.timeEnd('encode index'); + return encoded; + } + case KeyType.WrapProvingKey: { + let index = value[1][1]; + console.time('encode wrap index'); + let encoded = wasm.caml_pasta_fq_plonk_index_encode(index); + console.timeEnd('encode wrap index'); + return encoded; + } default: throw Error('todo'); } } function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { - console.log('DECODE', key); let wasm = getWasm(); switch (key[0]) { - case KeyType.StepProvingKey: + case KeyType.StepProvingKey: { let srs = Pickles.loadSrsFp(); + console.time('decode index'); let index = wasm.caml_pasta_fp_plonk_index_decode(bytes, srs); + console.timeEnd('decode index'); let cs = key[1][4]; return [KeyType.StepProvingKey, [0, index, cs]]; + } + case KeyType.WrapProvingKey: { + let srs = Pickles.loadSrsFq(); + console.time('decode wrap index'); + let index = wasm.caml_pasta_fq_plonk_index_decode(bytes, srs); + console.timeEnd('decode wrap index'); + let cs = key[1][3]; + return [KeyType.WrapProvingKey, [0, index, cs]]; + } default: throw Error('todo'); } From 1f54f64ef34437c0f8852a59ad0f69d515445a47 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 20 Oct 2023 12:12:16 +0200 Subject: [PATCH 0280/1215] fix length doc comment Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e7fc53ad6c..3ebc23b842 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -41,7 +41,7 @@ const Gadgets = { * * This gadget builds a chain of XOR gates recursively. Each XOR gate can verify 16 bit at most. If your input elements exceed 16 bit, another XOR gate will be added to the chain. * - * The `length` parameter lets you define how many bits should be compared. + * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * * **Note:** Specifying a larger `length` parameter adds additional constraints. * From 7c01398d683936ae541f67f3b32b7f0ec8935d35 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 20 Oct 2023 12:12:39 +0200 Subject: [PATCH 0281/1215] fix doc comment Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 3ebc23b842..8038922656 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -45,9 +45,8 @@ const Gadgets = { * * **Note:** Specifying a larger `length` parameter adds additional constraints. * - * **Note:** Both {@link Field} elements need to fit into `2^length - 1`, or the operation will fail. - * For example, for `length = 2` ( 2² = 4), `.xor` will fail for any element that is larger than `> 3`. - * If either input element exceeds the maximum bit length, an error is thrown and no proof can be generated because the method fails to properly constrain the operation. + * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated.. + * For example, with `length = 2` (`paddedLength = 16`), `xor()` will fail for any input that is larger than `2**16`. * * ```typescript * let a = Field(5); // ... 000101 From d1626723f4d9a8cd38d646b0299ea3cdcbca75e2 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 20 Oct 2023 12:16:25 +0200 Subject: [PATCH 0282/1215] fix size assertion --- src/lib/gadgets/bitwise.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 8e2465d3d5..dc1e026e2e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -15,17 +15,17 @@ function xor(a: Field, b: Field, length: number) { `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let l = 16; + let padLength = Math.ceil(length / l) * l; + // handle constant case if (a.isConstant() && b.isConstant()) { - assert( - a.toBigInt() < 2 ** length, - `${a.toBigInt()} does not fit into ${length} bits` - ); + let max = 1n << BigInt(padLength); - assert( - b.toBigInt() < 2 ** length, - `${b.toBigInt()} does not fit into ${length} bits` - ); + assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${max} bits`); + + assert(b.toBigInt() < max, `${b.toBigInt()} does not fit into ${max} bits`); return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } @@ -36,18 +36,14 @@ function xor(a: Field, b: Field, length: number) { () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) ); - // obtain pad length until the length is a multiple of 16 for n-bit length lookup table - let l = 16; - let padLength = Math.ceil(length / l) * l; - - // recursively build xor gadget chain + // builds the xor gadget chain buildXor(a, b, outputXor, padLength); // return the result of the xor operation return outputXor; } -// builds xor chain recursively +// builds a xor chain function buildXor( a: Field, b: Field, From 09ad52ef6ea360a7107af07fb48f66ac623d5b97 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 20 Oct 2023 12:26:20 +0200 Subject: [PATCH 0283/1215] Update src/lib/gadgets/bitwise.ts Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/bitwise.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index dc1e026e2e..b61b8fdcbf 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -23,9 +23,9 @@ function xor(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${max} bits`); + assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); - assert(b.toBigInt() < max, `${b.toBigInt()} does not fit into ${max} bits`); + assert(b.toBigInt() < max, `${b.toBigInt()} does not fit into ${padLength} bits`); return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } From b360fda053afc573f5304d20aa12b9f2d309d5ff Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 12:32:04 +0200 Subject: [PATCH 0284/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 70358c321b..b8c624fd12 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 70358c321b18988b81d89bbc47f9ea55459da056 +Subproject commit b8c624fd1261190b697b375930310d5c7f0891f1 From e28cecdf550968f586023347de9fdd2a135fcb81 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 14:31:56 +0200 Subject: [PATCH 0285/1215] start exposing dummy proofs but there's a problem --- src/lib/proof_system.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 553137d0fd..47bd0c3340 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -139,6 +139,22 @@ class Proof { this.proof = proof; // TODO optionally convert from string? this.maxProofsVerified = maxProofsVerified; } + + static async dummy( + publicInput: Input, + publicOutput: OutPut, + maxProofsVerified: 0 | 1 | 2 + ): Promise> { + // TODO need to select dummy based on maxProofsVerified + let dummyBase64 = await dummyBase64Proof(); + let [, dummyRaw] = Pickles.proofOfBase64(dummyBase64, maxProofsVerified); + return new this({ + publicInput, + publicOutput, + proof: dummyRaw, + maxProofsVerified, + }); + } } async function verify( From 786e3982370742176c96ba462b187666786f46ba Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 14:57:07 +0200 Subject: [PATCH 0286/1215] use flexible dummy proof --- src/bindings | 2 +- src/lib/proof_system.ts | 13 ++++++++----- src/snarky.d.ts | 9 +++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/bindings b/src/bindings index 69904ab544..f876f85498 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 69904ab5445b12bd8bf3e87f6ac34c70e64e1add +Subproject commit f876f854989f1431815452df9d3a25422debcb46 diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 47bd0c3340..a64be30170 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -145,9 +145,7 @@ class Proof { publicOutput: OutPut, maxProofsVerified: 0 | 1 | 2 ): Promise> { - // TODO need to select dummy based on maxProofsVerified - let dummyBase64 = await dummyBase64Proof(); - let [, dummyRaw] = Pickles.proofOfBase64(dummyBase64, maxProofsVerified); + let dummyRaw = await dummyProof(maxProofsVerified); return new this({ publicInput, publicOutput, @@ -859,8 +857,13 @@ ZkProgram.Proof = function < }; }; -function dummyBase64Proof() { - return withThreadPool(async () => Pickles.dummyBase64Proof()); +function dummyProof(maxProofsVerified: 0 | 1 | 2) { + return withThreadPool(async () => Pickles.dummyProof(maxProofsVerified)[1]); +} + +async function dummyBase64Proof() { + let proof = await dummyProof(2); + return Pickles.proofToBase64([2, proof]); } // what feature flags to set to enable certain gate types diff --git a/src/snarky.d.ts b/src/snarky.d.ts index f3e4d1e9e4..19844ad907 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -650,17 +650,18 @@ declare const Pickles: { verificationKey: string ): Promise; - dummyBase64Proof: () => string; + dummyProof: (maxProofsVerified: N) => [N, Pickles.Proof]; + /** * @returns (base64 vk, hash) */ dummyVerificationKey: () => [_: 0, data: string, hash: FieldConst]; proofToBase64: (proof: [0 | 1 | 2, Pickles.Proof]) => string; - proofOfBase64: ( + proofOfBase64: ( base64: string, - maxProofsVerified: 0 | 1 | 2 - ) => [0 | 1 | 2, Pickles.Proof]; + maxProofsVerified: N + ) => [N, Pickles.Proof]; proofToBase64Transaction: (proof: Pickles.Proof) => string; }; From 78c4de342d0e186c76af4e957501e08768992d87 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 15:54:23 +0200 Subject: [PATCH 0287/1215] expose domainLog2 --- src/lib/proof_system.ts | 13 ++++++++----- src/snarky.d.ts | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index a64be30170..4296e8b808 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -143,9 +143,10 @@ class Proof { static async dummy( publicInput: Input, publicOutput: OutPut, - maxProofsVerified: 0 | 1 | 2 + maxProofsVerified: 0 | 1 | 2, + domainLog2: number = 14 ): Promise> { - let dummyRaw = await dummyProof(maxProofsVerified); + let dummyRaw = await dummyProof(maxProofsVerified, domainLog2); return new this({ publicInput, publicOutput, @@ -857,12 +858,14 @@ ZkProgram.Proof = function < }; }; -function dummyProof(maxProofsVerified: 0 | 1 | 2) { - return withThreadPool(async () => Pickles.dummyProof(maxProofsVerified)[1]); +function dummyProof(maxProofsVerified: 0 | 1 | 2, domainLog2: number) { + return withThreadPool( + async () => Pickles.dummyProof(maxProofsVerified, domainLog2)[1] + ); } async function dummyBase64Proof() { - let proof = await dummyProof(2); + let proof = await dummyProof(2, 15); return Pickles.proofToBase64([2, proof]); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 19844ad907..7fb7f27fb8 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -650,7 +650,10 @@ declare const Pickles: { verificationKey: string ): Promise; - dummyProof: (maxProofsVerified: N) => [N, Pickles.Proof]; + dummyProof: ( + maxProofsVerified: N, + domainLog2: number + ) => [N, Pickles.Proof]; /** * @returns (base64 vk, hash) From bb138bf66601343b15914eb9da61fa046a0ba0cd Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 15:59:18 +0200 Subject: [PATCH 0288/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f876f85498..6db2442764 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f876f854989f1431815452df9d3a25422debcb46 +Subproject commit 6db2442764e48977dc25145b5b048f272e3995f5 From 600b6327a01b664795d3448f8e93d3009b507e1e Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 16:26:25 +0200 Subject: [PATCH 0289/1215] add docs --- CHANGELOG.md | 3 +++ src/lib/proof_system.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e64b6d357..9401e4ee36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 +- `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 + - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 4296e8b808..6d3987bde3 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -140,6 +140,27 @@ class Proof { this.maxProofsVerified = maxProofsVerified; } + /** + * Dummy proof. This can be useful for ZkPrograms that handle the base case in the same + * method as the inductive case, using a pattern like this: + * + * ```ts + * method(proof: SelfProof, isRecursive: Bool) { + * proof.verifyIf(isRecursive); + * // ... + * } + * ``` + * + * To use such a method in the base case, you need a dummy proof: + * + * ```ts + * let dummy = await MyProof.dummy(publicInput, publicOutput, 1); + * await myProgram.myMethod(dummy, Bool(false)); + * ``` + * + * **Note**: The types of `publicInput` and `publicOutput`, as well as the `maxProofsVerified` parameter, + * must match your ZkProgram. `maxProofsVerified` is the maximum number of proofs that any of your methods take as arguments. + */ static async dummy( publicInput: Input, publicOutput: OutPut, From 2d379a5fa5171ac580adcaf423015a44d98a9980 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 20 Oct 2023 17:33:25 +0200 Subject: [PATCH 0290/1215] deprecate Experimental.ZkProgram --- CHANGELOG.md | 4 ++++ src/examples/program-with-input.ts | 6 +++--- src/examples/program.ts | 6 +++--- src/index.ts | 9 +++++++-- src/tests/inductive-proofs-small.ts | 6 +++--- src/tests/inductive-proofs.ts | 10 +++++----- tests/integration/inductive-proofs.js | 10 +++++----- 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e64b6d357..5af5d4f1a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Constraint optimizations in Field methods and core crypto changes break all verification keys https://github.com/o1-labs/o1js/pull/1171 https://github.com/o1-labs/o1js/pull/1178 +### Changed + +- `ZkProgram` has moved out of the `Experimental` namespace and is now available as a top-level import directly. `Experimental.ZkProgram` has been deprecated. + ### Added - `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 diff --git a/src/examples/program-with-input.ts b/src/examples/program-with-input.ts index 71dfafbc2c..f506e61b07 100644 --- a/src/examples/program-with-input.ts +++ b/src/examples/program-with-input.ts @@ -1,7 +1,7 @@ import { SelfProof, Field, - Experimental, + ZkProgram, verify, isReady, Proof, @@ -11,7 +11,7 @@ import { await isReady; -let MyProgram = Experimental.ZkProgram({ +let MyProgram = ZkProgram({ publicInput: Field, methods: { @@ -35,7 +35,7 @@ let MyProgram = Experimental.ZkProgram({ MyProgram.publicInputType satisfies typeof Field; MyProgram.publicOutputType satisfies Provable; -let MyProof = Experimental.ZkProgram.Proof(MyProgram); +let MyProof = ZkProgram.Proof(MyProgram); console.log('program digest', MyProgram.digest()); diff --git a/src/examples/program.ts b/src/examples/program.ts index 111ed0b5a8..a60bbc54d5 100644 --- a/src/examples/program.ts +++ b/src/examples/program.ts @@ -1,7 +1,7 @@ import { SelfProof, Field, - Experimental, + ZkProgram, verify, isReady, Proof, @@ -12,7 +12,7 @@ import { await isReady; -let MyProgram = Experimental.ZkProgram({ +let MyProgram = ZkProgram({ publicOutput: Field, methods: { @@ -36,7 +36,7 @@ let MyProgram = Experimental.ZkProgram({ MyProgram.publicInputType satisfies Provable; MyProgram.publicOutputType satisfies typeof Field; -let MyProof = Experimental.ZkProgram.Proof(MyProgram); +let MyProof = ZkProgram.Proof(MyProgram); console.log('program digest', MyProgram.digest()); diff --git a/src/index.ts b/src/index.ts index ead07ffbea..6073135ff9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,7 +66,7 @@ export { setGraphqlEndpoints, setArchiveGraphqlEndpoint, sendZkapp, - Lightnet + Lightnet, } from './lib/fetch.js'; export * as Encryption from './lib/encryption.js'; export * as Encoding from './bindings/lib/encoding.js'; @@ -76,8 +76,10 @@ export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; export { Nullifier } from './lib/nullifier.js'; -// experimental APIs import { ZkProgram } from './lib/proof_system.js'; +export { ZkProgram }; + +// experimental APIs import { Callback } from './lib/zkapp.js'; import { createChildAccountUpdate } from './lib/account_update.js'; import { memoizeWitness } from './lib/provable.js'; @@ -97,6 +99,9 @@ type Callback_ = Callback; * (Not unstable in the sense that they are less functional or tested than other parts.) */ namespace Experimental { + /** @deprecated `ZkProgram` has moved out of the Experimental namespace and is now directly available as a top-level import `ZkProgram`. + * The old `Experimental.ZkProgram` API has been deprecated in favor of the new `ZkProgram` top-level import. + */ export let ZkProgram = Experimental_.ZkProgram; export let createChildAccountUpdate = Experimental_.createChildAccountUpdate; export let memoizeWitness = Experimental_.memoizeWitness; diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index 271a2bb6cc..5f6c063709 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -1,7 +1,7 @@ import { SelfProof, Field, - Experimental, + ZkProgram, isReady, shutdown, Proof, @@ -10,7 +10,7 @@ import { tic, toc } from '../examples/zkapps/tictoc.js'; await isReady; -let MaxProofsVerifiedOne = Experimental.ZkProgram({ +let MaxProofsVerifiedOne = ZkProgram({ publicInput: Field, methods: { @@ -45,7 +45,7 @@ async function testRecursion( ) { console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - let ProofClass = Experimental.ZkProgram.Proof(Program); + let ProofClass = ZkProgram.Proof(Program); tic('executing base case'); let initialProof = await Program.baseCase(Field(0)); diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index 61c1c9dc1d..9accd6a4b5 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -1,7 +1,7 @@ import { SelfProof, Field, - Experimental, + ZkProgram, isReady, shutdown, Proof, @@ -10,7 +10,7 @@ import { tic, toc } from '../examples/zkapps/tictoc.js'; await isReady; -let MaxProofsVerifiedZero = Experimental.ZkProgram({ +let MaxProofsVerifiedZero = ZkProgram({ publicInput: Field, methods: { @@ -24,7 +24,7 @@ let MaxProofsVerifiedZero = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedOne = Experimental.ZkProgram({ +let MaxProofsVerifiedOne = ZkProgram({ publicInput: Field, methods: { @@ -47,7 +47,7 @@ let MaxProofsVerifiedOne = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedTwo = Experimental.ZkProgram({ +let MaxProofsVerifiedTwo = ZkProgram({ publicInput: Field, methods: { @@ -100,7 +100,7 @@ async function testRecursion( ) { console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - let ProofClass = Experimental.ZkProgram.Proof(Program); + let ProofClass = ZkProgram.Proof(Program); tic('executing base case'); let initialProof = await Program.baseCase(Field(0)); diff --git a/tests/integration/inductive-proofs.js b/tests/integration/inductive-proofs.js index 5fe77f4ced..9238d67117 100644 --- a/tests/integration/inductive-proofs.js +++ b/tests/integration/inductive-proofs.js @@ -1,7 +1,7 @@ import { SelfProof, Field, - Experimental, + ZkProgram, isReady, shutdown, } from '../../dist/node/index.js'; @@ -9,7 +9,7 @@ import { tic, toc } from './tictoc.js'; await isReady; -let MaxProofsVerifiedZero = Experimental.ZkProgram({ +let MaxProofsVerifiedZero = ZkProgram({ publicInput: Field, methods: { @@ -23,7 +23,7 @@ let MaxProofsVerifiedZero = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedOne = Experimental.ZkProgram({ +let MaxProofsVerifiedOne = ZkProgram({ publicInput: Field, methods: { @@ -46,7 +46,7 @@ let MaxProofsVerifiedOne = Experimental.ZkProgram({ }, }); -let MaxProofsVerifiedTwo = Experimental.ZkProgram({ +let MaxProofsVerifiedTwo = ZkProgram({ publicInput: Field, methods: { @@ -92,7 +92,7 @@ await testRecursion(MaxProofsVerifiedTwo, 2); async function testRecursion(Program, maxProofsVerified) { console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - let ProofClass = Experimental.ZkProgram.Proof(Program); + let ProofClass = ZkProgram.Proof(Program); tic('executing base case..'); let initialProof = await Program.baseCase(Field(0)); From 31f0ede0ff2ded9f6d506a427358bd10399746ed Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 20 Oct 2023 22:16:26 +0200 Subject: [PATCH 0291/1215] wrap vk encoding --- src/lib/proof-system/prover-keys.ts | 26 +++++++++++++++++++++++++- src/lib/proof_system.ts | 2 +- src/snarky.d.ts | 3 +++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index c6bb05fe73..a7f30972e3 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -5,6 +5,7 @@ import { import { Pickles, getWasm } from '../../snarky.js'; export { encodeProverKey, decodeProverKey, AnyKey, AnyValue }; +export type { MlWrapVerificationKey }; type TODO = unknown; type Opaque = unknown; @@ -39,6 +40,13 @@ type MlWrapProvingKeyHeader = [ constraintSystem: MlConstraintSystem ]; +// Pickles.Verification_key.t +// no point in defining + +class MlWrapVerificationKey { + // opaque type +} + // pickles_bindings.ml, any_key enum enum KeyType { @@ -60,7 +68,7 @@ type AnyValue = | [KeyType.StepProvingKey, MlBackendKeyPair] | [KeyType.StepVerificationKey, TODO] | [KeyType.WrapProvingKey, MlBackendKeyPair] - | [KeyType.WrapVerificationKey, TODO]; + | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; function encodeProverKey(value: AnyValue): Uint8Array { let wasm = getWasm(); @@ -79,7 +87,15 @@ function encodeProverKey(value: AnyValue): Uint8Array { console.timeEnd('encode wrap index'); return encoded; } + case KeyType.WrapVerificationKey: { + let vk = value[1]; + console.time('encode wrap vk'); + let string = Pickles.encodeVerificationKey(vk); + console.timeEnd('encode wrap vk'); + return new TextEncoder().encode(string); + } default: + value[0] satisfies KeyType.StepVerificationKey; throw Error('todo'); } } @@ -103,7 +119,15 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { let cs = key[1][3]; return [KeyType.WrapProvingKey, [0, index, cs]]; } + case KeyType.WrapVerificationKey: { + let string = new TextDecoder().decode(bytes); + console.time('decode wrap vk'); + let vk = Pickles.decodeVerificationKey(string); + console.timeEnd('decode wrap vk'); + return [KeyType.WrapVerificationKey, vk]; + } default: + key[0] satisfies KeyType.StepVerificationKey; throw Error('todo'); } } diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 40cc1a082d..42b0ab6f37 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -554,7 +554,7 @@ async function compileProgram({ let bytes = read(path); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { - console.log('read failed', e.message); + console.log('read failed', e); return MlResult.unitError(); } }, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 9bd2a1d051..f7d8e41acb 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -685,6 +685,9 @@ declare const Pickles: { */ dummyVerificationKey: () => [_: 0, data: string, hash: FieldConst]; + encodeVerificationKey: (vk: ProverKeys.MlWrapVerificationKey) => string; + decodeVerificationKey: (vk: string) => ProverKeys.MlWrapVerificationKey; + proofToBase64: (proof: [0 | 1 | 2, Pickles.Proof]) => string; proofOfBase64: ( base64: string, From e795fb46984017737b029f9cb55d37db6f655b0c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 21 Oct 2023 16:58:02 -0700 Subject: [PATCH 0292/1215] chore(bindings): update subproject commit hash to aa7f880 for latest changes in the subproject --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 038a16eac1..aa7f880eb7 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 038a16eac1bf5dc62779a079b95fa36e21aa4202 +Subproject commit aa7f880eb752c91408c90a40cf7e8bcb7dec87ff From 044267254a643d2f7e2e18811ae6d8d0625c15f7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 21 Oct 2023 17:08:58 -0700 Subject: [PATCH 0293/1215] refactor(gadgets.ts): move testRot function and related tests to gadgets.unit-test.ts --- src/examples/gadgets.ts | 40 ------------- src/lib/gadgets/gadgets.unit-test.ts | 86 ++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 21a9c0f48b..345d5b9d71 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,45 +1,5 @@ import { Field, Provable, Experimental, Gadgets } from 'o1js'; -function testRot( - word: Field, - bits: number, - mode: 'left' | 'right', - result: Field -) { - Provable.runAndCheck(() => { - let w = Provable.witness(Field, () => word); - let r = Provable.witness(Field, () => result); - let output = Gadgets.rot(w, bits, mode); - Provable.asProver(() => { - Provable.log(`rot(${word}, ${bits}, ${mode}) = ${output}`); - }); - output.assertEquals(r, `rot(${word}, ${bits}, ${mode})`); - }); -} - -console.log('Running positive tests...'); -testRot(Field('0'), 0, 'left', Field('0')); -testRot(Field('0'), 32, 'right', Field('0')); -testRot(Field('1'), 1, 'left', Field('2')); -testRot(Field('1'), 63, 'left', Field('9223372036854775808')); -testRot(Field('256'), 4, 'right', Field('16')); -testRot(Field('1234567890'), 32, 'right', Field('5302428712241725440')); -testRot(Field('2651214356120862720'), 32, 'right', Field('617283945')); -testRot(Field('1153202983878524928'), 32, 'right', Field('268500993')); -testRot( - Field('6510615555426900570'), - 4, - 'right', - Field('11936128518282651045') -); -testRot( - Field('6510615555426900570'), - 4, - 'right', - Field('11936128518282651045') -); -console.log('🎉 Passed positive tests'); - let cs = Provable.constraintSystem(() => { let res1 = Provable.witness(Field, () => { let f = Field(12); diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 11def1e147..f758a57be6 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -1,5 +1,5 @@ import { mod } from '../../bindings/crypto/finite_field.js'; -import { Field } from '../field.js'; +import { Field } from '../../lib/core.js'; import { ZkProgram } from '../proof_system.js'; import { Spec, @@ -9,8 +9,19 @@ import { } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; import { Gadgets } from './gadgets.js'; +import { Provable } from '../provable.js'; + +let maybeUint64: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), +}; // TODO: make a ZkFunction or something that doesn't go through Pickles +// -------------------------- +// RangeCheck64 Gate +// -------------------------- let RangeCheck64 = ZkProgram({ methods: { @@ -23,31 +34,9 @@ let RangeCheck64 = ZkProgram({ }, }); -let ROT = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.rot(x, 2, 'left'); - Gadgets.rot(x, 2, 'right'); - }, - }, - }, -}); - await RangeCheck64.compile(); -await ROT.compile(); -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), -}; - -// do a couple of proofs // TODO: we use this as a test because there's no way to check custom gates quickly :( - await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); @@ -59,6 +48,22 @@ await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( } ); +// -------------------------- +// ROT Gate +// -------------------------- +let ROT = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(x) { + Gadgets.rot(x, 2, 'left'); + Gadgets.rot(x, 2, 'right'); + }, + }, + }, +}); + +await ROT.compile(); await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( (x) => { if (x >= 1n << 64n) throw Error('expected 64 bits'); @@ -69,3 +74,38 @@ await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( return await ROT.verify(proof); } ); + +function testRot( + word: Field, + bits: number, + mode: 'left' | 'right', + result: Field +) { + Provable.runAndCheck(() => { + let w = Provable.witness(Field, () => word); + let r = Provable.witness(Field, () => result); + let output = Gadgets.rot(w, bits, mode); + output.assertEquals(r, `rot(${word}, ${bits}, ${mode})`); + }); +} + +testRot(Field('0'), 0, 'left', Field('0')); +testRot(Field('0'), 32, 'right', Field('0')); +testRot(Field('1'), 1, 'left', Field('2')); +testRot(Field('1'), 63, 'left', Field('9223372036854775808')); +testRot(Field('256'), 4, 'right', Field('16')); +testRot(Field('1234567890'), 32, 'right', Field('5302428712241725440')); +testRot(Field('2651214356120862720'), 32, 'right', Field('617283945')); +testRot(Field('1153202983878524928'), 32, 'right', Field('268500993')); +testRot( + Field('6510615555426900570'), + 4, + 'right', + Field('11936128518282651045') +); +testRot( + Field('6510615555426900570'), + 4, + 'right', + Field('11936128518282651045') +); From 5b0de018434176bab2f55f56b7ee20bf0742ecc3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 21 Oct 2023 17:13:54 -0700 Subject: [PATCH 0294/1215] refactor(gadgets.unit-test.ts): convert inputs for rot to strings --- src/lib/gadgets/gadgets.unit-test.ts | 30 ++++++++++------------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index f758a57be6..914fed654c 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -89,23 +89,13 @@ function testRot( }); } -testRot(Field('0'), 0, 'left', Field('0')); -testRot(Field('0'), 32, 'right', Field('0')); -testRot(Field('1'), 1, 'left', Field('2')); -testRot(Field('1'), 63, 'left', Field('9223372036854775808')); -testRot(Field('256'), 4, 'right', Field('16')); -testRot(Field('1234567890'), 32, 'right', Field('5302428712241725440')); -testRot(Field('2651214356120862720'), 32, 'right', Field('617283945')); -testRot(Field('1153202983878524928'), 32, 'right', Field('268500993')); -testRot( - Field('6510615555426900570'), - 4, - 'right', - Field('11936128518282651045') -); -testRot( - Field('6510615555426900570'), - 4, - 'right', - Field('11936128518282651045') -); +testRot(Field(0), 0, 'left', Field(0)); +testRot(Field(0), 32, 'right', Field(0)); +testRot(Field(1), 1, 'left', Field(2)); +testRot(Field(1), 63, 'left', Field('9223372036854775808')); +testRot(Field(256), 4, 'right', Field(16)); +testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); +testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); +testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); +testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); +testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); From de2c09139b67d7fdb392a12a4524b3a8bc240e5d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 21 Oct 2023 17:16:56 -0700 Subject: [PATCH 0295/1215] refactor(gadgets.ts): simplify witness creation and rotation operations --- src/examples/gadgets.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 345d5b9d71..78800439ca 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,14 +1,10 @@ import { Field, Provable, Experimental, Gadgets } from 'o1js'; let cs = Provable.constraintSystem(() => { - let res1 = Provable.witness(Field, () => { - let f = Field(12); - return Gadgets.rot(f, 2, 'left'); - }); - let res2 = Provable.witness(Field, () => { - let f = Field(12); - return Gadgets.rot(f, 2, 'right'); - }); + let f = Provable.witness(Field, () => Field(12)); + + let res1 = Gadgets.rot(f, 2, 'left'); + let res2 = Gadgets.rot(f, 2, 'right'); res1.assertEquals(Field(48)); res2.assertEquals(Field(3)); From e3264ecfc9c5bf824da51c4eaee30564b8e05e6a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 22 Oct 2023 13:45:13 -0700 Subject: [PATCH 0296/1215] refactor(rot.ts): modify witnessSlices and change inputs to rot gate --- src/lib/gadgets/rot.ts | 44 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index c1763d1cbb..eaacb5ebd4 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -71,20 +71,20 @@ function rotate( rotated, excess, [ - witnessSlices(bound, 52, 64), - witnessSlices(bound, 40, 52), - witnessSlices(bound, 28, 40), - witnessSlices(bound, 16, 28), + witnessSlices(bound, 52, 12), // bits 52-64 + witnessSlices(bound, 40, 12), // bits 40-52 + witnessSlices(bound, 28, 12), // bits 28-40 + witnessSlices(bound, 16, 12), // bits 16-28 ], [ - witnessSlices(bound, 14, 16), - witnessSlices(bound, 12, 14), - witnessSlices(bound, 10, 12), - witnessSlices(bound, 8, 10), - witnessSlices(bound, 6, 8), - witnessSlices(bound, 4, 6), - witnessSlices(bound, 2, 4), - witnessSlices(bound, 0, 2), + witnessSlices(bound, 14, 2), // bits 14-16 + witnessSlices(bound, 12, 2), // bits 12-14 + witnessSlices(bound, 10, 2), // bits 10-12 + witnessSlices(bound, 8, 2), // bits 8-10 + witnessSlices(bound, 6, 2), // bits 6-8 + witnessSlices(bound, 4, 2), // bits 4-6 + witnessSlices(bound, 2, 2), // bits 2-4 + witnessSlices(bound, 0, 2), // bits 0-2 ], Field.from(big2PowerRot) ); @@ -96,21 +96,13 @@ function rotate( return [rotated, excess, shifted]; } -function witnessSlices(f: Field, start: number, stop = -1) { - if (stop !== -1 && stop <= start) { - throw Error('stop must be greater than start'); - } - +// TODO: move to utils once https://github.com/o1-labs/o1js/pull/1177 is merged +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); return Provable.witness(Field, () => { - let bits = f.toBits(); - if (stop > bits.length) { - throw Error('stop must be less than bit-length'); - } - if (stop === -1) { - stop = bits.length; - } - - return Field.fromBits(bits.slice(start, stop)); + let mask = (1n << BigInt(length)) - 1n; + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & mask); }); } From 84cf95fe0decac6dbb93d0bd28461c51d0c8a60b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 13:06:39 +0200 Subject: [PATCH 0297/1215] step verification keys, and handle reading strings --- src/lib/proof-system/prover-keys.ts | 64 +++++++++++++++++++++++------ src/lib/proof_system.ts | 7 +++- src/lib/storable.ts | 21 ++++++---- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index a7f30972e3..e64c85a358 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -3,14 +3,14 @@ import { WasmPastaFqPlonkIndex, } from '../../bindings/compiled/node_bindings/plonk_wasm.cjs'; import { Pickles, getWasm } from '../../snarky.js'; +import { VerifierIndex } from '../../bindings/crypto/bindings/kimchi-types.js'; +import { getRustConversion } from '../../bindings/crypto/bindings.js'; -export { encodeProverKey, decodeProverKey, AnyKey, AnyValue }; +export { encodeProverKey, decodeProverKey, proverKeyType, AnyKey, AnyValue }; export type { MlWrapVerificationKey }; -type TODO = unknown; -type Opaque = unknown; - // Plonk_constraint_system.Make()().t + class MlConstraintSystem { // opaque type } @@ -28,7 +28,7 @@ type MlBackendKeyPair = [ type MlStepProvingKeyHeader = [ _: 0, typeEqual: number, - snarkKeysHeader: Opaque, + snarkKeysHeader: unknown, index: number, constraintSystem: MlConstraintSystem ]; @@ -36,12 +36,11 @@ type MlStepProvingKeyHeader = [ type MlWrapProvingKeyHeader = [ _: 0, typeEqual: number, - snarkKeysHeader: Opaque, + snarkKeysHeader: unknown, constraintSystem: MlConstraintSystem ]; // Pickles.Verification_key.t -// no point in defining class MlWrapVerificationKey { // opaque type @@ -60,13 +59,13 @@ enum KeyType { type AnyKey = | [KeyType.StepProvingKey, MlStepProvingKeyHeader] - | [KeyType.StepVerificationKey, TODO] + | [KeyType.StepVerificationKey, unknown] | [KeyType.WrapProvingKey, MlWrapProvingKeyHeader] - | [KeyType.WrapVerificationKey, TODO]; + | [KeyType.WrapVerificationKey, unknown]; type AnyValue = | [KeyType.StepProvingKey, MlBackendKeyPair] - | [KeyType.StepVerificationKey, TODO] + | [KeyType.StepVerificationKey, VerifierIndex] | [KeyType.WrapProvingKey, MlBackendKeyPair] | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; @@ -80,6 +79,19 @@ function encodeProverKey(value: AnyValue): Uint8Array { console.timeEnd('encode index'); return encoded; } + case KeyType.StepVerificationKey: { + let vkMl = value[1]; + console.time('create rust conversion'); + const rustConversion = getRustConversion(getWasm()); + console.timeEnd('create rust conversion'); + console.time('verifierIndexToRust'); + let vkWasm = rustConversion.fp.verifierIndexToRust(vkMl); + console.timeEnd('verifierIndexToRust'); + console.time('encode vk'); + let string = wasm.caml_pasta_fp_plonk_verifier_index_serialize(vkWasm); + console.timeEnd('encode vk'); + return new TextEncoder().encode(string); + } case KeyType.WrapProvingKey: { let index = value[1][1]; console.time('encode wrap index'); @@ -95,7 +107,7 @@ function encodeProverKey(value: AnyValue): Uint8Array { return new TextEncoder().encode(string); } default: - value[0] satisfies KeyType.StepVerificationKey; + value satisfies never; throw Error('todo'); } } @@ -111,6 +123,23 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { let cs = key[1][4]; return [KeyType.StepProvingKey, [0, index, cs]]; } + case KeyType.StepVerificationKey: { + let srs = Pickles.loadSrsFp(); + let string = new TextDecoder().decode(bytes); + console.time('decode vk'); + let vkWasm = wasm.caml_pasta_fp_plonk_verifier_index_deserialize( + srs, + string + ); + console.timeEnd('decode vk'); + console.time('create rust conversion'); + const rustConversion = getRustConversion(getWasm()); + console.timeEnd('create rust conversion'); + console.time('verifierIndexFromRust'); + let vkMl = rustConversion.fp.verifierIndexFromRust(vkWasm); + console.timeEnd('verifierIndexFromRust'); + return [KeyType.StepVerificationKey, vkMl]; + } case KeyType.WrapProvingKey: { let srs = Pickles.loadSrsFq(); console.time('decode wrap index'); @@ -127,7 +156,18 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { return [KeyType.WrapVerificationKey, vk]; } default: - key[0] satisfies KeyType.StepVerificationKey; + key satisfies never; throw Error('todo'); } } + +const proverKeySerializationType = { + [KeyType.StepProvingKey]: 'bytes', + [KeyType.StepVerificationKey]: 'string', + [KeyType.WrapProvingKey]: 'bytes', + [KeyType.WrapVerificationKey]: 'string', +} as const; + +function proverKeyType(key: AnyKey): 'string' | 'bytes' { + return proverKeySerializationType[key[0]]; +} diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 42b0ab6f37..17d1d3e73c 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -32,6 +32,7 @@ import { Storable } from './storable.js'; import { decodeProverKey, encodeProverKey, + proverKeyType, } from './proof-system/prover-keys.js'; // public API @@ -551,7 +552,8 @@ async function compileProgram({ 0, function read_(key, path) { try { - let bytes = read(path); + let type = proverKeyType(key); + let bytes = read(path, type); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { console.log('read failed', e); @@ -560,8 +562,9 @@ async function compileProgram({ }, function write_(key, value, path) { try { + let type = proverKeyType(key); let bytes = encodeProverKey(value); - write(path, bytes); + write(path, bytes, type); return MlResult.ok(undefined); } catch (e: any) { console.log('write failed', e.message); diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 7e573a0ad5..5764b914e2 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -8,22 +8,29 @@ export { Storable }; * `read()` and `write()` can just throw errors on failure. */ type Storable = { - read(key: string): T; - write(key: string, value: T): void; + read(key: string, type: 'string' | 'bytes'): T; + write(key: string, value: T, type: 'string' | 'bytes'): void; }; const FileSystem = (cacheDirectory: string): Storable => ({ - read(key) { + read(key, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('READ', key); - let buffer = readFileSync(`${cacheDirectory}/${key}`); - return new Uint8Array(buffer.buffer); + if (type === 'string') { + let string = readFileSync(`${cacheDirectory}/${key}`, 'utf8'); + return new TextEncoder().encode(string); + } else { + let buffer = readFileSync(`${cacheDirectory}/${key}`); + return new Uint8Array(buffer.buffer); + } }, - write(key, value) { + write(key, value, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('WRITE', key); mkdirSync(cacheDirectory, { recursive: true }); - writeFileSync(`${cacheDirectory}/${key}`, value); + writeFileSync(`${cacheDirectory}/${key}`, value, { + encoding: type === 'string' ? 'utf8' : undefined, + }); }, }); From 82c812655fda4d9ab4ae7fb9687839449b5996d8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 15:18:16 +0200 Subject: [PATCH 0298/1215] default to not caching --- src/lib/proof_system.ts | 4 ++-- src/lib/storable.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 17d1d3e73c..14bed6df39 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -523,7 +523,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - storable: { read, write } = Storable.FileSystemDefault, + storable: { read, write } = Storable.None, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -556,7 +556,7 @@ async function compileProgram({ let bytes = read(path, type); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { - console.log('read failed', e); + console.log('read failed', e.message); return MlResult.unitError(); } }, diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 5764b914e2..2789d83c70 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -12,6 +12,15 @@ type Storable = { write(key: string, value: T, type: 'string' | 'bytes'): void; }; +const None: Storable = { + read() { + throw Error('not available'); + }, + write() { + throw Error('not available'); + }, +}; + const FileSystem = (cacheDirectory: string): Storable => ({ read(key, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); @@ -39,4 +48,5 @@ const FileSystemDefault = FileSystem('/tmp'); const Storable = { FileSystem, FileSystemDefault, + None, }; From 2b28f522770050d223f453871ff6f80b6b5f7b2c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 15:21:32 +0200 Subject: [PATCH 0299/1215] remove generic type parameter which is always used with the same type --- src/lib/proof_system.ts | 2 +- src/lib/storable.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 14bed6df39..67f39dddca 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -532,7 +532,7 @@ async function compileProgram({ methods: ((...args: any) => void)[]; gates: Gate[][]; proofSystemTag: { name: string }; - storable?: Storable; + storable?: Storable; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 2789d83c70..bff1daec16 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -7,12 +7,12 @@ export { Storable }; * Interface for storing and retrieving values for caching. * `read()` and `write()` can just throw errors on failure. */ -type Storable = { - read(key: string, type: 'string' | 'bytes'): T; - write(key: string, value: T, type: 'string' | 'bytes'): void; +type Storable = { + read(key: string, type: 'string' | 'bytes'): Uint8Array; + write(key: string, value: Uint8Array, type: 'string' | 'bytes'): void; }; -const None: Storable = { +const None: Storable = { read() { throw Error('not available'); }, @@ -21,7 +21,7 @@ const None: Storable = { }, }; -const FileSystem = (cacheDirectory: string): Storable => ({ +const FileSystem = (cacheDirectory: string): Storable => ({ read(key, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('READ', key); From 184a5b20acae885e5bfa6246375871f52e296ccf Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 16:23:50 +0200 Subject: [PATCH 0300/1215] use a proper cache directory --- package-lock.json | 14 ++++++++++++++ package.json | 1 + src/lib/proof_system.ts | 2 +- src/lib/storable.ts | 4 ++-- src/lib/util/fs.ts | 3 +++ src/lib/util/fs.web.ts | 5 +++++ 6 files changed, 26 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49d8af9547..e8dc3eebf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", + "cachedir": "^2.4.0", "detect-gpu": "^5.0.5", "isomorphic-fetch": "^3.0.0", "js-sha256": "^0.9.0", @@ -2620,6 +2621,14 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9761,6 +9770,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", diff --git a/package.json b/package.json index 1ade0ac845..f2e35405d6 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ }, "dependencies": { "blakejs": "1.2.1", + "cachedir": "^2.4.0", "detect-gpu": "^5.0.5", "isomorphic-fetch": "^3.0.0", "js-sha256": "^0.9.0", diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 67f39dddca..adc6b08a21 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -523,7 +523,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - storable: { read, write } = Storable.None, + storable: { read, write } = Storable.FileSystemDefault, overrideWrapDomain, }: { publicInputType: ProvablePure; diff --git a/src/lib/storable.ts b/src/lib/storable.ts index bff1daec16..6e492cfd73 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -1,4 +1,4 @@ -import { writeFileSync, readFileSync, mkdirSync } from './util/fs.js'; +import { writeFileSync, readFileSync, mkdirSync, cacheDir } from './util/fs.js'; import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; export { Storable }; @@ -43,7 +43,7 @@ const FileSystem = (cacheDirectory: string): Storable => ({ }, }); -const FileSystemDefault = FileSystem('/tmp'); +const FileSystemDefault = FileSystem(cacheDir('pickles')); const Storable = { FileSystem, diff --git a/src/lib/util/fs.ts b/src/lib/util/fs.ts index e4e61589f5..4d08832363 100644 --- a/src/lib/util/fs.ts +++ b/src/lib/util/fs.ts @@ -1,2 +1,5 @@ +import cachedir from 'cachedir'; + export { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; export { resolve } from 'node:path'; +export { cachedir as cacheDir }; diff --git a/src/lib/util/fs.web.ts b/src/lib/util/fs.web.ts index 1cc5bd4c8f..6810aff2fd 100644 --- a/src/lib/util/fs.web.ts +++ b/src/lib/util/fs.web.ts @@ -3,8 +3,13 @@ export { dummy as readFileSync, dummy as resolve, dummy as mkdirSync, + cacheDir, }; function dummy() { throw Error('not implemented'); } + +function cacheDir() { + return ''; +} From b6284b249030654de50abd74786321cb16f3183a Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Mon, 23 Oct 2023 14:53:06 +0000 Subject: [PATCH 0301/1215] Add AND gate --- src/bindings | 2 +- src/examples/gadgets.ts | 38 ++++++++++- src/examples/primitive_constraint_system.ts | 8 +++ src/examples/regression_test.json | 4 ++ src/lib/gadgets/bitwise.ts | 70 +++++++++++++++++++-- src/lib/gadgets/bitwise.unit-test.ts | 25 ++++++++ src/lib/gadgets/gadgets.ts | 32 +++++++++- src/lib/gates.ts | 18 +++++- src/snarky.d.ts | 11 ++++ 9 files changed, 198 insertions(+), 10 deletions(-) diff --git a/src/bindings b/src/bindings index 83810ae030..100c625cde 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 83810ae030f0a57a2df9fd0a90a4911b32bcb34b +Subproject commit 100c625cde92070057463b4ecec75667cab553d4 diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 2048b294fa..273e9c7b50 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -15,6 +15,8 @@ const XOR = Experimental.ZkProgram({ }, }); +console.log('XOR:'); + console.log('compiling..'); console.time('compile'); @@ -24,8 +26,40 @@ console.timeEnd('compile'); console.log('proving..'); console.time('prove'); -let proof = await XOR.baseCase(); +let XORproof = await XOR.baseCase(); +console.timeEnd('prove'); + +if (!(await XOR.verify(XORproof))) throw Error('Invalid proof'); +else console.log('proof valid'); + +const AND = Experimental.ZkProgram({ + methods: { + baseCase: { + privateInputs: [], + method: () => { + let a = Provable.witness(Field, () => Field(3)); + let b = Provable.witness(Field, () => Field(5)); + let actual = Gadgets.and(a, b, 4); + let expected = Field(1); + actual.assertEquals(expected); + }, + }, + }, +}); + +console.log('AND:'); + +console.log('compiling..'); + +console.time('compile'); +await AND.compile(); +console.timeEnd('compile'); + +console.log('proving..'); + +console.time('prove'); +let ANDproof = await AND.baseCase(); console.timeEnd('prove'); -if (!(await XOR.verify(proof))) throw Error('Invalid proof'); +if (!(await AND.verify(ANDproof))) throw Error('Invalid proof'); else console.log('proof valid'); diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 1ef5a5f87c..656aa2e3a2 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -72,6 +72,14 @@ const BitwiseMock = { Gadgets.xor(a, b, 48); Gadgets.xor(a, b, 64); }, + and() { + let a = Provable.witness(Field, () => new Field(5n)); + let b = Provable.witness(Field, () => new Field(5n)); + Gadgets.and(a, b, 16); + Gadgets.and(a, b, 32); + Gadgets.and(a, b, 48); + Gadgets.and(a, b, 64); + }, }; export const GroupCS = mock(GroupMock, 'Group Primitive'); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 817796fa3a..340b8f9fca 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -171,6 +171,10 @@ "xor": { "rows": 15, "digest": "b3595a9cc9562d4f4a3a397b6de44971" + }, + "and": { + "rows": 19, + "digest": "f18a6905deba799225051cdd89ef2606" } }, "verificationKey": { diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index b61b8fdcbf..a59ab873d8 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,9 +1,9 @@ import { Provable } from '../provable.js'; import { Field as Fp } from '../../provable/field-bigint.js'; -import { Field } from '../field.js'; +import { Field, FieldConst } from '../field.js'; import * as Gates from '../gates.js'; -export { xor }; +export { xor, and }; function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive @@ -23,9 +23,15 @@ function xor(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); + assert( + a.toBigInt() < max, + `${a.toBigInt()} does not fit into ${padLength} bits` + ); - assert(b.toBigInt() < max, `${b.toBigInt()} does not fit into ${padLength} bits`); + assert( + b.toBigInt() < max, + `${b.toBigInt()} does not fit into ${padLength} bits` + ); return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } @@ -106,6 +112,62 @@ function buildXor( zero.assertEquals(expectedOutput); } +function and(a: Field, b: Field, length: number) { + // check that both input lengths are positive + assert(length > 0, `Input lengths need to be positive values.`); + + // check that length does not exceed maximum field size in bits + assert( + length <= Field.sizeInBits(), + `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + ); + + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let padLength = Math.ceil(length / 16) * 16; + + // handle constant case + if (a.isConstant() && b.isConstant()) { + let max = 1n << BigInt(padLength); + + assert( + a.toBigInt() < max, + `${a.toBigInt()} does not fit into ${padLength} bits` + ); + + assert( + b.toBigInt() < max, + `${b.toBigInt()} does not fit into ${padLength} bits` + ); + + return new Field(Fp.and(a.toBigInt(), b.toBigInt())); + } + + // calculate expect and output + let outputAnd = Provable.witness( + Field, + () => new Field(Fp.and(a.toBigInt(), b.toBigInt())) + ); + + // compute values for gate + let sum = a.add(b); + let xor_output = xor(a, b, length); + let and_output = outputAnd; + + Gates.basic( + FieldConst['1'], + sum, + FieldConst['-1'], + xor_output, + FieldConst.fromBigint(-2n), + and_output, + FieldConst['0'], + FieldConst['0'] + ); + + // return the result of the and operation + return outputAnd; +} + function assert(stmt: boolean, message?: string) { if (!stmt) { throw Error(message ?? 'Assertion failed'); diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4b92ce7aff..5fb6073646 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,6 +20,12 @@ let Bitwise = ZkProgram({ return Gadgets.xor(a, b, 64); }, }, + and: { + privateInputs: [Field, Field], + method(a: Field, b: Field) { + return Gadgets.and(a, b, 64); + }, + }, }, }); @@ -32,6 +38,10 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); Fp.xor, (x, y) => Gadgets.xor(x, y, length) ); + equivalent({ from: [uint(length), uint(length)], to: field })( + Fp.and, + (x, y) => Gadgets.and(x, y, length) + ); }); let maybeUint64: Spec = { @@ -56,3 +66,18 @@ await equivalentAsync( return proof.publicOutput; } ); + +await equivalentAsync( + { from: [maybeUint64, maybeUint64], to: field }, + { runs: 3 } +)( + (x, y) => { + if (x >= 2n ** 64n || y >= 2n ** 64n) + throw Error('Does not fit into 64 bits'); + return Fp.and(x, y); + }, + async (x, y) => { + let proof = await Bitwise.and(x, y); + return proof.publicOutput; + } +); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8038922656..bab7055dfa 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { xor } from './bitwise.js'; +import { xor, and } from './bitwise.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -34,7 +34,6 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, - /** * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. @@ -59,4 +58,33 @@ const Gadgets = { xor(a: Field, b: Field, length: number) { return xor(a, b, length); }, + /** + * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * An AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a one double generic gate (plus the gates created by XOR) the that verifies the following relationship between these values. + *  a + b = sum + * a x b = xor + * a ^ b = and + * + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`. + * + * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. + * + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * + * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated.. + * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. + * + * ```typescript + * let a = Field(3); // ... 000011 + * let b = Field(5); // ... 000101 + * + * let c = and(a, b, 2); // ... 000001 + * c.assertEquals(1); + * ``` + */ + and(a: Field, b: Field, length: number) { + return and(a, b, length); + }, }; diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 1a5a9a3c00..0c026a4dfe 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64, xor, zero }; +export { rangeCheck64, xor, zero, basic }; /** * Asserts that x is at most 64 bits @@ -81,6 +81,22 @@ function xor( ); } +/** + * Generic gate + */ +function basic( + sl: FieldConst, + l: Field, + sr: FieldConst, + r: Field, + so: FieldConst, + o: Field, + sm: FieldConst, + sc: FieldConst +) { + Snarky.gates.basic(sl, l.value, sr, r.value, so, o.value, sm, sc); +} + function zero(a: Field, b: Field, c: Field) { Snarky.gates.zero(a.value, b.value, c.value); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index ab0f5e85a9..a90f5dc3d7 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -326,6 +326,17 @@ declare const Snarky: { ): void; zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; + + basic( + sl: FieldConst, + l: FieldVar, + sr: FieldConst, + r: FieldVar, + so: FieldConst, + o: FieldVar, + sm: FieldConst, + sc: FieldConst + ): void; }; bool: { From c0409dabcffd9641e6a5d5ec6512e22579fe3035 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 16:53:21 +0200 Subject: [PATCH 0302/1215] expose storable in public APIs, harden cache dir resolving --- src/index.ts | 1 + src/lib/proof_system.ts | 7 ++++--- src/lib/storable.ts | 43 ++++++++++++++++++++++++++++++++++++----- src/lib/zkapp.ts | 4 +++- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index b65a42c11f..dd615dbd6e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ export { Undefined, Void, } from './lib/proof_system.js'; +export { Storable } from './lib/storable.js'; export { Token, diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index adc6b08a21..8fb2e8edce 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -278,7 +278,7 @@ function ZkProgram< } | undefined; - async function compile() { + async function compile({ storable = Storable.FileSystemDefault } = {}) { let methodsMeta = analyzeMethods(); let gates = methodsMeta.map((m) => m.gates); let { provers, verify, verificationKey } = await compileProgram({ @@ -288,6 +288,7 @@ function ZkProgram< methods: methodFunctions, gates, proofSystemTag: selfTag, + storable, overrideWrapDomain: config.overrideWrapDomain, }); compileOutput = { provers, verify }; @@ -523,7 +524,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - storable: { read, write } = Storable.FileSystemDefault, + storable: { read, write }, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -532,7 +533,7 @@ async function compileProgram({ methods: ((...args: any) => void)[]; gates: Gate[][]; proofSystemTag: { name: string }; - storable?: Storable; + storable: Storable; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 6e492cfd73..0a57d9f819 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -1,14 +1,34 @@ -import { writeFileSync, readFileSync, mkdirSync, cacheDir } from './util/fs.js'; +import { + writeFileSync, + readFileSync, + mkdirSync, + resolve, + cacheDir, +} from './util/fs.js'; import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; export { Storable }; /** - * Interface for storing and retrieving values for caching. + * Interface for storing and retrieving values, for caching. * `read()` and `write()` can just throw errors on failure. */ type Storable = { + /** + * Read a value from the cache. + * + * @param key The key to read from the cache. Can safely be used as a file path. + * @param type Specifies whether the data to be read is a utf8-encoded string or raw binary data. This was added + * because node's `fs.readFileSync` returns garbage when reading string files without specifying the encoding. + */ read(key: string, type: 'string' | 'bytes'): Uint8Array; + /** + * Write a value to the cache. + * + * @param key The key of the data to write to the cache. This will be used by `read()` to retrieve the data. Can safely be used as a file path. + * @param value The value to write to the cache, as a byte array. + * @param type Specifies whether the value originated from a utf8-encoded string or raw binary data. + */ write(key: string, value: Uint8Array, type: 'string' | 'bytes'): void; }; @@ -26,10 +46,10 @@ const FileSystem = (cacheDirectory: string): Storable => ({ if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('READ', key); if (type === 'string') { - let string = readFileSync(`${cacheDirectory}/${key}`, 'utf8'); + let string = readFileSync(resolve(cacheDirectory, key), 'utf8'); return new TextEncoder().encode(string); } else { - let buffer = readFileSync(`${cacheDirectory}/${key}`); + let buffer = readFileSync(resolve(cacheDirectory, key)); return new Uint8Array(buffer.buffer); } }, @@ -37,7 +57,7 @@ const FileSystem = (cacheDirectory: string): Storable => ({ if (jsEnvironment !== 'node') throw Error('file system not available'); console.log('WRITE', key); mkdirSync(cacheDirectory, { recursive: true }); - writeFileSync(`${cacheDirectory}/${key}`, value, { + writeFileSync(resolve(cacheDirectory, key), value, { encoding: type === 'string' ? 'utf8' : undefined, }); }, @@ -46,7 +66,20 @@ const FileSystem = (cacheDirectory: string): Storable => ({ const FileSystemDefault = FileSystem(cacheDir('pickles')); const Storable = { + /** + * Store data on the file system, in a directory of your choice. + * + * Note: this {@link Storable} only caches data in Node.js. + */ FileSystem, + /** + * Store data on the file system, in a standard cache directory depending on the OS. + * + * Note: this {@link Storable} only caches data in Node.js. + */ FileSystemDefault, + /** + * Don't store anything. + */ None, }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index cf91b86108..b6aeccc9e3 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -56,6 +56,7 @@ import { inProver, snarkContext, } from './provable-context.js'; +import { Storable } from './storable.js'; // external API export { @@ -661,7 +662,7 @@ class SmartContract { * it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**, * up to several minutes if your circuit is large or your hardware is not optimal for these operations. */ - static async compile() { + static async compile({ storable = Storable.FileSystemDefault } = {}) { let methodIntfs = this._methods ?? []; let methods = methodIntfs.map(({ methodName }) => { return ( @@ -688,6 +689,7 @@ class SmartContract { methods, gates, proofSystemTag: this, + storable, }); let verificationKey = { data: verificationKey_.data, From 6a411d58c1bc95c168b7eed06fc3eecfda00c61c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 16:54:01 +0200 Subject: [PATCH 0303/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index b8c624fd12..610e7bbc40 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b8c624fd1261190b697b375930310d5c7f0891f1 +Subproject commit 610e7bbc4053009a1a921e61067f5c85f29357f3 From 5f42063533a373c62503ad76e04b39bd79d53841 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 23 Oct 2023 17:06:30 +0200 Subject: [PATCH 0304/1215] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb508bf0b..31d31d446b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 +### Changed + +- Use cached prover keys in `compile()` when running in Node.js https://github.com/o1-labs/o1js/pull/1187 + - Caching is configurable by passing a custom `Storable` (new export) to `compile()` + - By default, prover keys are stored in an OS-dependent cache directory; `~/.cache/pickles` on Mac and Linux + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes From 97af12c9978e926138dd90275774675b19602567 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 09:29:51 -0700 Subject: [PATCH 0305/1215] refactor(rot.ts): simplify direction param usage --- src/bindings | 2 +- src/lib/gadgets/rot.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index aa7f880eb7..8ee9bde95e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit aa7f880eb752c91408c90a40cf7e8bcb7dec87ff +Subproject commit 8ee9bde95e0ad6947eb303e4346f147deba10ed2 diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index eaacb5ebd4..43b3cdaeb7 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -20,7 +20,7 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { } if (word.isConstant()) { - return new Field(Fp.rot(word.toBigInt(), bits, direction === 'left')); + return new Field(Fp.rot(word.toBigInt(), bits, direction)); } const [rotated] = rotate(word, bits, direction); return rotated; From 20f10a0652a80de0f479132f249dad8d72f78c07 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 09:36:08 -0700 Subject: [PATCH 0306/1215] refactor(field.unit-test.ts): remove rotation test from field.unit-test.ts --- src/lib/field.unit-test.ts | 17 ----------------- src/lib/gadgets/gadgets.unit-test.ts | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 28e17b90eb..8f7d8843f5 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -17,7 +17,6 @@ import { bool, Spec, } from './testing/equivalent.js'; -import { Gadgets } from './gadgets/gadgets.js'; // types Field satisfies Provable; @@ -45,22 +44,6 @@ test(Random.field, Random.json.field, (x, y, assert) => { assert(z.toJSON() === y); }); -// rotation -test( - Random.uint64, - Random.nat(64), - Random.boolean, - (x, n, direction, assert) => { - let z = Field(x); - let r1 = Fp.rot(x, n, direction); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rot(f, n, direction ? 'left' : 'right'); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - } -); - // handles small numbers test(Random.nat(1000), (n, assert) => { assert(Field(n).toString() === String(n)); diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 914fed654c..74380b1862 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -7,7 +7,8 @@ import { equivalentAsync, field, } from '../testing/equivalent.js'; -import { Random } from '../testing/random.js'; +import { test, Random } from '../testing/property.js'; +import { Field as Fp } from '../../provable/field-bigint.js'; import { Gadgets } from './gadgets.js'; import { Provable } from '../provable.js'; @@ -99,3 +100,19 @@ testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); + +// rotation +test( + Random.uint64, + Random.nat(64), + Random.boolean, + (x, n, direction, assert) => { + let z = Field(x); + let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let r2 = Gadgets.rot(f, n, direction ? 'left' : 'right'); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + } +); From 9696ff0a714f48356bd76735efc7c6116df96290 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 09:44:03 -0700 Subject: [PATCH 0307/1215] fix(gadgets.ts): change expectedRight from integer to Field object to match the type of actualRight for correct comparison --- src/examples/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 78800439ca..58b1ef882f 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -26,7 +26,7 @@ const ROT = Experimental.ZkProgram({ let expectedLeft = Field(192); actualLeft.assertEquals(expectedLeft); - let expectedRight = 12; + let expectedRight = Field(12); actualRight.assertEquals(expectedRight); }, }, From 27efd595a3b7f5c779024ebc54aa74c0c1f0227e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 09:44:44 -0700 Subject: [PATCH 0308/1215] fix(rot.ts): use BigInt for comparison to handle large numbers correctly The change was necessary as the previous comparison could fail for large numbers. Now, the comparison is done using BigInt which can handle larger numbers accurately. --- src/lib/gadgets/rot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 43b3cdaeb7..4b093dc1fa 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -33,7 +33,7 @@ function rotate( ): [Field, Field, Field] { // Check as the prover, that the input word is at most 64 bits. Provable.asProver(() => { - if (word.toBigInt() > 2 ** MAX_BITS) { + if (word.toBigInt() > BigInt(2 ** MAX_BITS)) { throw Error( `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` ); From ef864c58ae6956c1d77cc1d49cc54fd43d1019ab Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 09:56:54 -0700 Subject: [PATCH 0309/1215] refactor(rot.ts): extract max bits check into a separate function to reduce code duplication --- src/lib/gadgets/rot.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 4b093dc1fa..695b5d974c 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -20,6 +20,7 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { } if (word.isConstant()) { + checkMaxBits(word); return new Field(Fp.rot(word.toBigInt(), bits, direction)); } const [rotated] = rotate(word, bits, direction); @@ -33,11 +34,7 @@ function rotate( ): [Field, Field, Field] { // Check as the prover, that the input word is at most 64 bits. Provable.asProver(() => { - if (word.toBigInt() > BigInt(2 ** MAX_BITS)) { - throw Error( - `rot: expected word to be at most 64 bits, got ${word.toBigInt()}` - ); - } + checkMaxBits(word); }); const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; @@ -96,6 +93,14 @@ function rotate( return [rotated, excess, shifted]; } +function checkMaxBits(x: Field) { + if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { + throw Error( + `rot: expected word to be at most 64 bits, got ${x.toBigInt()}` + ); + } +} + // TODO: move to utils once https://github.com/o1-labs/o1js/pull/1177 is merged function witnessSlices(f: Field, start: number, length: number) { if (length <= 0) throw Error('Length must be a positive number'); From 2a66015da3396bbc02ae4164cf19a66e53291014 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:43:53 +0000 Subject: [PATCH 0310/1215] Bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 100c625cde..f20ab7aa7d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 100c625cde92070057463b4ecec75667cab553d4 +Subproject commit f20ab7aa7de403b410ee7f34823c6d0d80736043 From 29e2e9d1700385f3291d4377ac9b3ca766c8b914 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:25:22 -0700 Subject: [PATCH 0311/1215] feat(gadgets.ts): add leftShift function to perform left shift operation on Field elements This function is similar to the '<<' shift operation in JavaScript. It uses the rotation method internally to perform the operation. It throws an error if the input value exceeds 64 bits. --- src/lib/gadgets/gadgets.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 01fe77b052..fd8f28da2a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -62,4 +62,31 @@ const Gadgets = { rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { return rot(word, bits, direction); }, + + /** + * Performs a left shift operation on the provided {@link Field} element. + * This is akin to the `<<` shift operation in JavaScript, where bits are moved to the left. + * The `leftShift` function uses the rotation method internally to achieve this operation. + * + * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. + * For elements that exceed 64 bits, this operation will fail. + * + * @param word {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the left. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12)); + * const y = leftShift(x, 2); // left shift by 2 bits + * y.assertEquals(48); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + leftShift(word: Field, bits: number) { + return rot(word, bits, 'left'); + }, }; From c8f6776e3574bbf338a0a3a7e9092841ed48eb70 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:29:17 -0700 Subject: [PATCH 0312/1215] feat(gadgets.ts): add rightShift function to perform right shift operation on Field elements This function is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. It throws an error if the input value exceeds 64 bits. This feature enhances the functionality of the Gadgets library by providing more operations for Field elements. --- src/lib/gadgets/gadgets.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index fd8f28da2a..56e98dd743 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -89,4 +89,31 @@ const Gadgets = { leftShift(word: Field, bits: number) { return rot(word, bits, 'left'); }, + + /** + * Performs a right shift operation on the provided {@link Field} element. + * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. + * The `rightShift` function utilizes the rotation method internally to implement this operation. + * + * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. + * For elements that exceed 64 bits, this operation will fail. + * + * @param word {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the right. + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(48)); + * const y = rightShift(x, 2); // right shift by 2 bits + * y.assertEquals(12); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + rightShift(word: Field, bits: number) { + return rot(word, bits, 'right'); + }, }; From 1a9e56a768048a021e2f6e3baa5e0ae48dc906d2 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:38:31 -0700 Subject: [PATCH 0313/1215] chore(bindings): update subproject commit hash to 055d2887 for latest changes and improvements --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 8ee9bde95e..055d288727 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 8ee9bde95e0ad6947eb303e4346f147deba10ed2 +Subproject commit 055d288727888e298a2a9815ecebb628db9ebfbf From c4a261e41175addbbdf4cb9f0c7dbb8fe1bc67d7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:43:18 -0700 Subject: [PATCH 0314/1215] refactor(gadgets.ts, gadgets.unit-test.ts, rot.ts, gates.ts, snarky.d.ts): rename 'word' to 'field' for better clarity and consistency in code --- src/lib/gadgets/gadgets.ts | 6 +++--- src/lib/gadgets/gadgets.unit-test.ts | 6 +++--- src/lib/gadgets/rot.ts | 26 +++++++++++++------------- src/lib/gates.ts | 4 ++-- src/snarky.d.ts | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 01fe77b052..dc834c2218 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -41,7 +41,7 @@ const Gadgets = { * * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. * - * @param word {@link Field} element to rotate. + * @param field {@link Field} element to rotate. * @param bits amount of bits to rotate this {@link Field} element with. * @param direction left or right rotation direction. * @@ -59,7 +59,7 @@ const Gadgets = { * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits * ``` */ - rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { - return rot(word, bits, direction); + rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rot(field, bits, direction); }, }; diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 74380b1862..cfd91adedd 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -77,16 +77,16 @@ await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( ); function testRot( - word: Field, + field: Field, bits: number, mode: 'left' | 'right', result: Field ) { Provable.runAndCheck(() => { - let w = Provable.witness(Field, () => word); + let w = Provable.witness(Field, () => field); let r = Provable.witness(Field, () => result); let output = Gadgets.rot(w, bits, mode); - output.assertEquals(r, `rot(${word}, ${bits}, ${mode})`); + output.assertEquals(r, `rot(${field}, ${bits}, ${mode})`); }); } diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 695b5d974c..32efb313f5 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -7,7 +7,7 @@ export { rot, rotate }; const MAX_BITS = 64 as const; -function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { +function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); @@ -19,22 +19,22 @@ function rot(word: Field, bits: number, direction: 'left' | 'right' = 'left') { ); } - if (word.isConstant()) { - checkMaxBits(word); - return new Field(Fp.rot(word.toBigInt(), bits, direction)); + if (field.isConstant()) { + checkMaxBits(field); + return new Field(Fp.rot(field.toBigInt(), bits, direction)); } - const [rotated] = rotate(word, bits, direction); + const [rotated] = rotate(field, bits, direction); return rotated; } function rotate( - word: Field, + field: Field, bits: number, direction: 'left' | 'right' = 'left' ): [Field, Field, Field] { - // Check as the prover, that the input word is at most 64 bits. + // Check as the prover, that the input is at most 64 bits. Provable.asProver(() => { - checkMaxBits(word); + checkMaxBits(field); }); const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; @@ -44,12 +44,12 @@ function rotate( const [rotated, excess, shifted, bound] = Provable.witness( Provable.Array(Field, 4), () => { - const wordBigInt = word.toBigInt(); + const f = field.toBigInt(); // Obtain rotated output, excess, and shifted for the equation: - // word * 2^rot = excess * 2^64 + shifted + // f * 2^rot = excess * 2^64 + shifted const { quotient: excess, remainder: shifted } = divideWithRemainder( - wordBigInt * big2PowerRot, + f * big2PowerRot, big2Power64 ); @@ -64,7 +64,7 @@ function rotate( // Compute current row Gates.rot( - word, + field, rotated, excess, [ @@ -96,7 +96,7 @@ function rotate( function checkMaxBits(x: Field) { if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { throw Error( - `rot: expected word to be at most 64 bits, got ${x.toBigInt()}` + `rot: expected field to be at most 64 bits, got ${x.toBigInt()}` ); } } diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 175dc72afc..30701da882 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -44,7 +44,7 @@ function rangeCheck64(x: Field) { } function rot( - word: Field, + field: Field, rotated: Field, excess: Field, limbs: [Field, Field, Field, Field], @@ -52,7 +52,7 @@ function rot( two_to_rot: Field ) { Snarky.gates.rot( - word.value, + field.value, rotated.value, excess.value, MlArray.to(limbs.map((x) => x.value)), diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 8c80abff4a..3bee503990 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -308,7 +308,7 @@ declare const Snarky: { ): void; rot( - word: FieldVar, + field: FieldVar, rotated: FieldVar, excess: FieldVar, limbs: MlArray, From 0f31fee49e26d693100a9f7efecb7e8ccb1b0fb1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:45:27 -0700 Subject: [PATCH 0315/1215] fix(rot.ts): replace 'rotated' with 'field' in rangeCheck64 function to correct the variable being checked --- src/lib/gadgets/rot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 32efb313f5..565d616be7 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -89,7 +89,7 @@ function rotate( Gates.rangeCheck64(shifted); // Compute following row Gates.rangeCheck64(excess); - Gates.rangeCheck64(rotated); + Gates.rangeCheck64(field); return [rotated, excess, shifted]; } From 10b8be92d9dd547e3f91d99b16e5b743a13a9041 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 12:58:04 -0700 Subject: [PATCH 0316/1215] fix(regression_test.json): update digest value in rot method to ensure test consistency with latest changes --- src/examples/regression_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index ef26739ab3..3ee8cc35e5 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -170,7 +170,7 @@ "methods": { "rot": { "rows": 17, - "digest": "5253728d9fd357808be58a39c8375c07" + "digest": "916f4017a60f48d56d487c6869919b9c" } }, "verificationKey": { From f42089dbd60fda619c9f2429b35139e01a1aa41f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 16:11:24 -0700 Subject: [PATCH 0317/1215] refactor(gadgets.ts): rename 'word' parameter to 'field' in leftShift and rightShift methods for better clarity and consistency with documentation --- src/lib/gadgets/gadgets.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f7719c5d12..77673f3b3f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -71,7 +71,7 @@ const Gadgets = { * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. * For elements that exceed 64 bits, this operation will fail. * - * @param word {@link Field} element to shift. + * @param field {@link Field} element to shift. * @param bits Amount of bits to shift the {@link Field} element to the left. * * @throws Throws an error if the input value exceeds 64 bits. @@ -86,8 +86,8 @@ const Gadgets = { * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ - leftShift(word: Field, bits: number) { - return rot(word, bits, 'left'); + leftShift(field: Field, bits: number) { + return rot(field, bits, 'left'); }, /** @@ -98,7 +98,7 @@ const Gadgets = { * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. * For elements that exceed 64 bits, this operation will fail. * - * @param word {@link Field} element to shift. + * @param field {@link Field} element to shift. * @param bits Amount of bits to shift the {@link Field} element to the right. * * @throws Throws an error if the input value exceeds 64 bits. @@ -113,7 +113,7 @@ const Gadgets = { * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ - rightShift(word: Field, bits: number) { - return rot(word, bits, 'right'); + rightShift(field: Field, bits: number) { + return rot(field, bits, 'right'); }, }; From 3b30edb66065bafaa590a87997eb582771f844aa Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 16:37:36 -0700 Subject: [PATCH 0318/1215] feat(rot.ts): add leftShift function to handle left shift operations --- src/lib/gadgets/gadgets.ts | 4 ++-- src/lib/gadgets/rot.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 77673f3b3f..2e125b4e15 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { rot } from './rot.js'; +import { rot, leftShift } from './rot.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -87,7 +87,7 @@ const Gadgets = { * ``` */ leftShift(field: Field, bits: number) { - return rot(field, bits, 'left'); + return leftShift(field, bits); }, /** diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 565d616be7..585bd39dab 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -3,7 +3,7 @@ import { Provable } from '../provable.js'; import { Fp } from '../../bindings/crypto/finite_field.js'; import * as Gates from '../gates.js'; -export { rot, rotate }; +export { rot, rotate, leftShift }; const MAX_BITS = 64 as const; @@ -27,6 +27,20 @@ function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { return rotated; } +function leftShift(field: Field, bits: number) { + // Check that the rotation bits are in range + if (bits < 0 || bits > MAX_BITS) { + throw Error(`leftShift: expected bits to be between 0 and 64, got ${bits}`); + } + + if (field.isConstant()) { + checkMaxBits(field); + return new Field(Fp.leftShift(field.toBigInt(), bits)); + } + const [, , shifted] = rotate(field, bits, 'left'); + return shifted; +} + function rotate( field: Field, bits: number, From ac3a1afdad5667dc2cf0806e1e5da6a65def6931 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 16:39:40 -0700 Subject: [PATCH 0319/1215] feat(gadgets.ts, rot.ts): add rightShift function --- src/lib/gadgets/gadgets.ts | 4 ++-- src/lib/gadgets/rot.ts | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 2e125b4e15..1b45220eae 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { rot, leftShift } from './rot.js'; +import { rot, leftShift, rightShift } from './rot.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -114,6 +114,6 @@ const Gadgets = { * ``` */ rightShift(field: Field, bits: number) { - return rot(field, bits, 'right'); + return rightShift(field, bits); }, }; diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 585bd39dab..4f6492a3e9 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -3,7 +3,7 @@ import { Provable } from '../provable.js'; import { Fp } from '../../bindings/crypto/finite_field.js'; import * as Gates from '../gates.js'; -export { rot, rotate, leftShift }; +export { rot, rotate, leftShift, rightShift }; const MAX_BITS = 64 as const; @@ -41,6 +41,22 @@ function leftShift(field: Field, bits: number) { return shifted; } +function rightShift(field: Field, bits: number) { + // Check that the rotation bits are in range + if (bits < 0 || bits > MAX_BITS) { + throw Error( + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); + } + + if (field.isConstant()) { + checkMaxBits(field); + return new Field(Fp.rightShift(field.toBigInt(), bits)); + } + const [, excess] = rotate(field, bits, 'right'); + return excess; +} + function rotate( field: Field, bits: number, From 743ce690ed5a286cb8fd2fc849ee92281bbadc2c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 17:16:57 -0700 Subject: [PATCH 0320/1215] fix(rot.ts): swap leftShift and rightShift functions --- src/lib/gadgets/rot.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 4f6492a3e9..839ed24705 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -27,34 +27,34 @@ function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { return rotated; } -function leftShift(field: Field, bits: number) { +function rightShift(field: Field, bits: number) { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { - throw Error(`leftShift: expected bits to be between 0 and 64, got ${bits}`); + throw Error( + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); } if (field.isConstant()) { checkMaxBits(field); - return new Field(Fp.leftShift(field.toBigInt(), bits)); + return new Field(Fp.rightShift(field.toBigInt(), bits)); } - const [, , shifted] = rotate(field, bits, 'left'); - return shifted; + const [, excess] = rotate(field, bits, 'right'); + return excess; } -function rightShift(field: Field, bits: number) { +function leftShift(field: Field, bits: number) { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { - throw Error( - `rightShift: expected bits to be between 0 and 64, got ${bits}` - ); + throw Error(`leftShift: expected bits to be between 0 and 64, got ${bits}`); } if (field.isConstant()) { checkMaxBits(field); - return new Field(Fp.rightShift(field.toBigInt(), bits)); + return new Field(Fp.leftShift(field.toBigInt(), bits)); } - const [, excess] = rotate(field, bits, 'right'); - return excess; + const [, , shifted] = rotate(field, bits, 'left'); + return shifted; } function rotate( From 942897d6cdee27fd54eb366b910b6ba2750fe5b9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 17:32:26 -0700 Subject: [PATCH 0321/1215] chore(bindings): update subproject commit hash to 3cd5ee9f for latest changes in the subproject --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 055d288727..3cd5ee9f66 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 055d288727888e298a2a9815ecebb628db9ebfbf +Subproject commit 3cd5ee9f666ca81a599f0e75da8e0983f170a728 From a5c61a398c35c512957f93c19a9da8fb9cd4f30a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 23 Oct 2023 17:33:10 -0700 Subject: [PATCH 0322/1215] feat(gadgets.unit-test.ts): add shift gates tests --- src/lib/gadgets/gadgets.unit-test.ts | 119 +++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index cfd91adedd..733ab89f99 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -11,6 +11,7 @@ import { test, Random } from '../testing/property.js'; import { Field as Fp } from '../../provable/field-bigint.js'; import { Gadgets } from './gadgets.js'; import { Provable } from '../provable.js'; +import { Bool } from '../core.js'; let maybeUint64: Spec = { ...field, @@ -116,3 +117,121 @@ test( }); } ); + +// -------------------------- +// Shift Gates +// -------------------------- + +function testShift( + field: Field, + bits: number, + mode: 'left' | 'right', + result: Field +) { + Provable.runAndCheck(() => { + let w = Provable.witness(Field, () => field); + let r = Provable.witness(Field, () => result); + let output = Provable.if( + Bool(mode === 'left'), + Gadgets.leftShift(w, bits), + Gadgets.rightShift(w, bits) + ); + output.assertEquals( + r, + `${mode === 'left' ? 'ls' : 'rs'}(${field}, ${bits})` + ); + }); +} + +let LS = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(x) { + Gadgets.leftShift(x, 2); + }, + }, + }, +}); + +await LS.compile(); +await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( + (x) => { + if (x >= 1n << 64n) throw Error('expected 64 bits'); + return true; + }, + async (x) => { + let proof = await LS.run(x); + return await LS.verify(proof); + } +); + +let RS = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(x) { + Gadgets.rightShift(x, 2); + }, + }, + }, +}); + +await RS.compile(); +await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( + (x) => { + if (x >= 1n << 64n) throw Error('expected 64 bits'); + return true; + }, + async (x) => { + let proof = await RS.run(x); + return await RS.verify(proof); + } +); + +testShift(Field(0), 1, 'left', Field(0)); +testShift(Field(0), 1, 'right', Field(0)); +testShift(Field(1), 1, 'left', Field(2)); +testShift(Field(1), 1, 'right', Field(0)); +testShift(Field(256), 4, 'right', Field(16)); +testShift(Field(256), 20, 'right', Field(0)); +testShift(Field(6510615555426900570n), 16, 'right', Field(99344109427290n)); +testShift( + Field(18446744073709551615n), + 15, + 'left', + Field(18446744073709518848n) +); +testShift(Field(12523523412423524646n), 32, 'right', Field(2915860016)); +testShift( + Field(12523523412423524646n), + 32, + 'left', + Field(17134720101237391360n) +); + +// TODO: left case is broken +test(Random.uint64, Random.nat(64), (x, n, assert) => { + let z = Field(x); + let r = Fp.leftShift(x, n); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let o = Gadgets.leftShift(f, n); + Provable.asProver(() => { + console.log( + `input: ${z}, shift: ${n}, expected: ${r}, actual: ${o.toBigInt()}` + ); + }); + Provable.asProver(() => assert(r === o.toBigInt())); + }); +}); + +test(Random.uint64, Random.nat(64), (x, n, assert) => { + let z = Field(x); + let r = Fp.rightShift(x, n); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let o = Gadgets.rightShift(f, n); + Provable.asProver(() => assert(r === o.toBigInt())); + }); +}); From e9d0d0f7e004e1c20c495d22d20226584d6f38df Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 24 Oct 2023 07:31:22 +0200 Subject: [PATCH 0323/1215] remove debug logs --- src/lib/proof-system/prover-keys.ts | 24 ------------------------ src/lib/proof_system.ts | 2 -- src/lib/storable.ts | 2 -- 3 files changed, 28 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index e64c85a358..dbaf832042 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -74,36 +74,24 @@ function encodeProverKey(value: AnyValue): Uint8Array { switch (value[0]) { case KeyType.StepProvingKey: { let index = value[1][1]; - console.time('encode index'); let encoded = wasm.caml_pasta_fp_plonk_index_encode(index); - console.timeEnd('encode index'); return encoded; } case KeyType.StepVerificationKey: { let vkMl = value[1]; - console.time('create rust conversion'); const rustConversion = getRustConversion(getWasm()); - console.timeEnd('create rust conversion'); - console.time('verifierIndexToRust'); let vkWasm = rustConversion.fp.verifierIndexToRust(vkMl); - console.timeEnd('verifierIndexToRust'); - console.time('encode vk'); let string = wasm.caml_pasta_fp_plonk_verifier_index_serialize(vkWasm); - console.timeEnd('encode vk'); return new TextEncoder().encode(string); } case KeyType.WrapProvingKey: { let index = value[1][1]; - console.time('encode wrap index'); let encoded = wasm.caml_pasta_fq_plonk_index_encode(index); - console.timeEnd('encode wrap index'); return encoded; } case KeyType.WrapVerificationKey: { let vk = value[1]; - console.time('encode wrap vk'); let string = Pickles.encodeVerificationKey(vk); - console.timeEnd('encode wrap vk'); return new TextEncoder().encode(string); } default: @@ -117,42 +105,30 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { switch (key[0]) { case KeyType.StepProvingKey: { let srs = Pickles.loadSrsFp(); - console.time('decode index'); let index = wasm.caml_pasta_fp_plonk_index_decode(bytes, srs); - console.timeEnd('decode index'); let cs = key[1][4]; return [KeyType.StepProvingKey, [0, index, cs]]; } case KeyType.StepVerificationKey: { let srs = Pickles.loadSrsFp(); let string = new TextDecoder().decode(bytes); - console.time('decode vk'); let vkWasm = wasm.caml_pasta_fp_plonk_verifier_index_deserialize( srs, string ); - console.timeEnd('decode vk'); - console.time('create rust conversion'); const rustConversion = getRustConversion(getWasm()); - console.timeEnd('create rust conversion'); - console.time('verifierIndexFromRust'); let vkMl = rustConversion.fp.verifierIndexFromRust(vkWasm); - console.timeEnd('verifierIndexFromRust'); return [KeyType.StepVerificationKey, vkMl]; } case KeyType.WrapProvingKey: { let srs = Pickles.loadSrsFq(); - console.time('decode wrap index'); let index = wasm.caml_pasta_fq_plonk_index_decode(bytes, srs); - console.timeEnd('decode wrap index'); let cs = key[1][3]; return [KeyType.WrapProvingKey, [0, index, cs]]; } case KeyType.WrapVerificationKey: { let string = new TextDecoder().decode(bytes); - console.time('decode wrap vk'); let vk = Pickles.decodeVerificationKey(string); - console.timeEnd('decode wrap vk'); return [KeyType.WrapVerificationKey, vk]; } default: diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 96781c0ccc..337f2aebc3 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -593,7 +593,6 @@ async function compileProgram({ let bytes = read(path, type); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { - console.log('read failed', e.message); return MlResult.unitError(); } }, @@ -604,7 +603,6 @@ async function compileProgram({ write(path, bytes, type); return MlResult.ok(undefined); } catch (e: any) { - console.log('write failed', e.message); return MlResult.unitError(); } }, diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 0a57d9f819..42046bae58 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -44,7 +44,6 @@ const None: Storable = { const FileSystem = (cacheDirectory: string): Storable => ({ read(key, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); - console.log('READ', key); if (type === 'string') { let string = readFileSync(resolve(cacheDirectory, key), 'utf8'); return new TextEncoder().encode(string); @@ -55,7 +54,6 @@ const FileSystem = (cacheDirectory: string): Storable => ({ }, write(key, value, type: 'string' | 'bytes') { if (jsEnvironment !== 'node') throw Error('file system not available'); - console.log('WRITE', key); mkdirSync(cacheDirectory, { recursive: true }); writeFileSync(resolve(cacheDirectory, key), value, { encoding: type === 'string' ? 'utf8' : undefined, From 35deab8f19d78a785c072a4e2c5db3202996f86f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 24 Oct 2023 09:06:07 +0200 Subject: [PATCH 0324/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index b4c598b5e3..e77f9f4db7 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b4c598b5e3d8300f2c1bef6cf453d10c26bdb9c8 +Subproject commit e77f9f4db784d6ef8c7a7836fcc4fa43444f9560 From 5df6d8bfb4956210d6bef4dd727e826ce601499b Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Oct 2023 10:37:55 +0200 Subject: [PATCH 0325/1215] address feedback --- src/lib/gadgets/bitwise.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index b61b8fdcbf..05ae43cbfa 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -16,16 +16,21 @@ function xor(a: Field, b: Field, length: number) { ); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table - let l = 16; - let padLength = Math.ceil(length / l) * l; + let padLength = Math.ceil(length / 16) * 16; // handle constant case if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); + assert( + a.toBigInt() < max, + `${a.toBigInt()} does not fit into ${padLength} bits` + ); - assert(b.toBigInt() < max, `${b.toBigInt()} does not fit into ${padLength} bits`); + assert( + b.toBigInt() < max, + `${b.toBigInt()} does not fit into ${padLength} bits` + ); return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } @@ -65,7 +70,7 @@ function buildXor( let in2_2 = witnessSlices(b, 8, 4); let in2_3 = witnessSlices(b, 12, 4); - // slice of expected output + // slices of expected output let out0 = witnessSlices(expectedOutput, 0, 4); let out1 = witnessSlices(expectedOutput, 4, 4); let out2 = witnessSlices(expectedOutput, 8, 4); From 13b0701507ae91159522320f702361af025167fe Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Oct 2023 10:38:54 +0200 Subject: [PATCH 0326/1215] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e64b6d357..78a1b895eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 +- Added bitwise `XOR` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1177 + ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) ### Breaking changes From fa408e8ef293e93e66a909f26e65c883d6c35539 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Oct 2023 10:56:09 +0200 Subject: [PATCH 0327/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 83810ae030..dbe878db43 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 83810ae030f0a57a2df9fd0a90a4911b32bcb34b +Subproject commit dbe878db43d256ac3085f248551b05b75ffecfda From 0cb77f2f9c53053d8498aaf4ff7d9c101c806610 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 24 Oct 2023 12:34:38 +0200 Subject: [PATCH 0328/1215] measure compile time --- src/examples/simple_zkapp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index f033266e86..d1bae21804 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -88,7 +88,9 @@ let zkapp = new SimpleZkapp(zkappAddress); if (doProofs) { console.log('compile'); + console.time('compile'); await SimpleZkapp.compile(); + console.timeEnd('compile'); } console.log('deploy'); From cc3aa525c028aa92ee2a5abdff119861a6fe5ca1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 24 Oct 2023 15:13:18 +0200 Subject: [PATCH 0329/1215] add script to update wasm types --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f2e35405d6..dafc4785cf 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", "make": "make -C ../../.. snarkyjs", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", + "wasm": "./src/bindings/scripts/update-wasm-and-types.sh", "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", From a3b0fc72cb25928c5a75ca9d4d73590f7936e501 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 24 Oct 2023 10:05:50 -0700 Subject: [PATCH 0330/1215] chore(bindings): update subproject commit hash to fd87c4ece761d35e05bf679165d49c577f4326ce for latest changes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 3cd5ee9f66..fd87c4ece7 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3cd5ee9f666ca81a599f0e75da8e0983f170a728 +Subproject commit fd87c4ece761d35e05bf679165d49c577f4326ce From 46b86b6df779c3e74251fce838fe2e9937a9b13f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 24 Oct 2023 10:07:00 -0700 Subject: [PATCH 0331/1215] fix(gadgets.unit-test.ts): correct leftShift test case by removing TODO comment, test case is not broken anymore --- src/lib/gadgets/gadgets.unit-test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index 733ab89f99..1eeaf4670e 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -210,18 +210,12 @@ testShift( Field(17134720101237391360n) ); -// TODO: left case is broken test(Random.uint64, Random.nat(64), (x, n, assert) => { let z = Field(x); let r = Fp.leftShift(x, n); Provable.runAndCheck(() => { let f = Provable.witness(Field, () => z); let o = Gadgets.leftShift(f, n); - Provable.asProver(() => { - console.log( - `input: ${z}, shift: ${n}, expected: ${r}, actual: ${o.toBigInt()}` - ); - }); Provable.asProver(() => assert(r === o.toBigInt())); }); }); From c6e644b42c7cc7f6cd92c7442958807091bd0aaf Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 24 Oct 2023 10:09:04 -0700 Subject: [PATCH 0332/1215] feat(primitive_constraint_system.ts): add leftShift and rightShift methods to BitwiseMock --- src/examples/primitive_constraint_system.ts | 10 ++++++++++ src/examples/regression_test.json | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index c680a1855b..09e330ba4d 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -71,6 +71,16 @@ const BitwiseMock = { Gadgets.rot(a, 4, 'left'); Gadgets.rot(a, 4, 'right'); }, + leftShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.leftShift(a, 2); + Gadgets.leftShift(a, 4); + }, + rightShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rightShift(a, 2); + Gadgets.rightShift(a, 4); + }, }; export const GroupCS = mock(GroupMock, 'Group Primitive'); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 3ee8cc35e5..a4253c80fa 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -171,6 +171,14 @@ "rot": { "rows": 17, "digest": "916f4017a60f48d56d487c6869919b9c" + }, + "leftShift": { + "rows": 9, + "digest": "70b1449c9a549b3aa2e964c667b7e14c" + }, + "rightShift": { + "rows": 9, + "digest": "110f686f60987e5e46b290e69e10cd37" } }, "verificationKey": { From 24e7a7448dccecf2d307dbb66799cb62bf1260fa Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 24 Oct 2023 10:27:15 -0700 Subject: [PATCH 0333/1215] refactor(rot.ts): extract bits range check into a separate function to reduce code duplication --- src/lib/gadgets/rot.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 839ed24705..19d5f48516 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -9,10 +9,7 @@ const MAX_BITS = 64 as const; function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); - } - + checkBitsRange(bits); if (direction !== 'left' && direction !== 'right') { throw Error( `rot: expected direction to be 'left' or 'right', got ${direction}` @@ -28,12 +25,8 @@ function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { } function rightShift(field: Field, bits: number) { - // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error( - `rightShift: expected bits to be between 0 and 64, got ${bits}` - ); - } + // Check that the shift bits are in range + checkBitsRange(bits); if (field.isConstant()) { checkMaxBits(field); @@ -44,10 +37,8 @@ function rightShift(field: Field, bits: number) { } function leftShift(field: Field, bits: number) { - // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`leftShift: expected bits to be between 0 and 64, got ${bits}`); - } + // Check that the shift bits are in range + checkBitsRange(bits); if (field.isConstant()) { checkMaxBits(field); @@ -123,6 +114,12 @@ function rotate( return [rotated, excess, shifted]; } +function checkBitsRange(bits: number) { + if (bits < 0 || bits > MAX_BITS) { + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); + } +} + function checkMaxBits(x: Field) { if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { throw Error( From 08a51150faaa6c5ef200bac22277e7c2f8eac677 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Oct 2023 20:06:14 +0200 Subject: [PATCH 0334/1215] adjust comments --- src/lib/gadgets/bitwise.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 05ae43cbfa..53e6c432a4 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -35,7 +35,7 @@ function xor(a: Field, b: Field, length: number) { return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); } - // calculate expect xor output + // calculate expected xor output let outputXor = Provable.witness( Field, () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) @@ -57,7 +57,7 @@ function buildXor( ) { // construct the chain of XORs until padLength is 0 while (padLength !== 0) { - // slices the inputs 4 4bit-sized chunks + // slices the inputs into 4x 4bit-sized chunks // slices of a let in1_0 = witnessSlices(a, 0, 4); let in1_1 = witnessSlices(a, 4, 4); From 09cad2cc9821934cf358adbab96c5529e7679ded Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Oct 2023 20:12:29 +0200 Subject: [PATCH 0335/1215] add disclaimer to doc comment --- src/lib/gadgets/gadgets.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8038922656..5f993a9727 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -44,6 +44,8 @@ const Gadgets = { * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * * **Note:** Specifying a larger `length` parameter adds additional constraints. + * It is also important to mention that specifying a smaller `length` allows the verifier to infer the length of the original input data (e.g. smaller than 16 bit if only one XOR gate has been used). + * A zkApp developer should consider these implications when choosing the `length` parameter and carefully weigh the trade-off between increased amount of constraints and security. * * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated.. * For example, with `length = 2` (`paddedLength = 16`), `xor()` will fail for any input that is larger than `2**16`. From ca6754b2ee54a1bd27e6cddfd748a52867fe935f Mon Sep 17 00:00:00 2001 From: Joseandro Luiz Date: Tue, 24 Oct 2023 15:45:24 -0300 Subject: [PATCH 0336/1215] Added CODEOWNERS file structure to help with x-team reviews --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..6b33083aff --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +/src/lib/gadgets @o1-labs/crypto-eng-reviewers \ No newline at end of file From f14284a14ed76510704d29e133608feb92f3f702 Mon Sep 17 00:00:00 2001 From: Joseandro Luiz Date: Tue, 24 Oct 2023 17:20:08 -0300 Subject: [PATCH 0337/1215] Update CODEOWNERS Co-authored-by: Gregor Mitscha-Baude --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6b33083aff..d878a07b61 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -/src/lib/gadgets @o1-labs/crypto-eng-reviewers \ No newline at end of file +/src/lib/gadgets @o1-labs/crypto-eng-reviewers @mitschabaude From 6e1fd44ff29c0f0ad9c7f963f2c8eec6d3f0a7fc Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 13:52:58 -0700 Subject: [PATCH 0338/1215] feat(gadgets.ts): add not function to Gadgets object to perform bitwise NOT operation on a field --- src/lib/gadgets/gadgets.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 5f993a9727..5123926f51 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -61,4 +61,8 @@ const Gadgets = { xor(a: Field, b: Field, length: number) { return xor(a, b, length); }, + + not(a: Field, length: number) { + + } }; From 65aa015489eb041f7ae71945fb004e753f433e18 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 14:02:51 -0700 Subject: [PATCH 0339/1215] feat(gadgets.ts): update implementation for the 'not' function in the Gadgets module --- src/lib/gadgets/gadgets.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 5123926f51..eab55dc21c 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -63,6 +63,8 @@ const Gadgets = { }, not(a: Field, length: number) { - - } + // mask with all bits set to 1, up to the specified length + const allOnes = Field((1n << BigInt(length)) - 1n); + return xor(a, allOnes, length); + }, }; From 359bcab796cf210206c215324692f56f8f54e38e Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 14:19:11 -0700 Subject: [PATCH 0340/1215] docs(gadgets.ts): add documentation for the `not` method in the Gadgets module The `not` method in the Gadgets module performs a bitwise NOT operation on a given input. It takes two parameters: `a`, the value to apply NOT to, and `length`, the number of bits to be considered for the NOT operation. The method uses the XOR gate to build the NOT operation, applying it only up to the specified bit length. The method documentation includes an example usage and a note about ensuring that the input value fits into the specified bit length to avoid potential errors. --- src/lib/gadgets/gadgets.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index eab55dc21c..0da2b8513a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -62,6 +62,25 @@ const Gadgets = { return xor(a, b, length); }, + + /** + * Bitwise NOT gadget on {@link Field} elements, for a specified bit length. Equivalent to the [bitwise NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT), but limited to the given length. + * A NOT gate works by inverting each bit. + * + * This gadget builds the NOT operation on a given input using the XOR gate. The NOT operation is applied only up to the specified bit length. + * + * **Note:** The {@link Field} element input needs to fit into the specified bit length. Otherwise, the `xor` implementation may throw an error. + * + * ```typescript + * let a = Field(5); // ... 000101 + * + * let c = not(a, 3); // ... 010 + * c.assertEquals(2); + * ``` + * + * @param a - The value to apply NOT to. + * @param length - The number of bits to be considered for the NOT operation. + */ not(a: Field, length: number) { // mask with all bits set to 1, up to the specified length const allOnes = Field((1n << BigInt(length)) - 1n); From 7b12e6956e76da6fce614c2988461adae8f914a7 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:25:22 +0000 Subject: [PATCH 0341/1215] Added link to Mina Book to explain AND gadget --- src/lib/gadgets/bitwise.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index a59ab873d8..3cf33abc1e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -149,6 +149,7 @@ function and(a: Field, b: Field, length: number) { ); // compute values for gate + // explanation here: https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and let sum = a.add(b); let xor_output = xor(a, b, length); let and_output = outputAnd; From d1058e41abea19a822fcdbe3d796d52d33f5b022 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:22:41 +0000 Subject: [PATCH 0342/1215] Log line between gadgets in example --- src/examples/gadgets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 273e9c7b50..08bb1942d9 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,5 +1,7 @@ import { Field, Provable, Gadgets, Experimental } from 'o1js'; +console.log('--------------- XOR ---------------'); + const XOR = Experimental.ZkProgram({ methods: { baseCase: { @@ -15,8 +17,6 @@ const XOR = Experimental.ZkProgram({ }, }); -console.log('XOR:'); - console.log('compiling..'); console.time('compile'); @@ -32,6 +32,8 @@ console.timeEnd('prove'); if (!(await XOR.verify(XORproof))) throw Error('Invalid proof'); else console.log('proof valid'); +console.log('--------------- AND ---------------'); + const AND = Experimental.ZkProgram({ methods: { baseCase: { @@ -47,8 +49,6 @@ const AND = Experimental.ZkProgram({ }, }); -console.log('AND:'); - console.log('compiling..'); console.time('compile'); From 7aaffae5eff79f0877ada7f1d15c76afea5590ef Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 16:52:13 -0700 Subject: [PATCH 0343/1215] feat(bitwise.ts): add not function to perform bitwise negation on a field The `not` function is added to perform bitwise negation on a field. This function takes two parameters: `a` which is the field to be negated, and `length` which is the number of bits in the field. Currently, the function is empty and needs to be implemented. --- src/lib/gadgets/bitwise.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 53e6c432a4..8ffdb45c09 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -3,7 +3,11 @@ import { Field as Fp } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; import * as Gates from '../gates.js'; -export { xor }; +export { xor, not }; + +function not(a: Field, length: number) { + +} function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive From 5bd215f1f7a46d59e8c77805bcc586d629a482d6 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:52:30 +0000 Subject: [PATCH 0344/1215] Made TypeDoc comment less bad --- src/lib/gadgets/gadgets.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index bab7055dfa..a50855742a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -62,12 +62,16 @@ const Gadgets = { * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). * An AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. * - * It can be checked by a one double generic gate (plus the gates created by XOR) the that verifies the following relationship between these values. - *  a + b = sum - * a x b = xor - * a ^ b = and + * It can be checked by a double generic gate the that verifies the following relationship between the values below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`). * - * `a + b = sum` and the conjunction equation `2 * and = sum - xor`. + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a x b = xor`\ + * `a ^ b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * From f0398e368485bcf281528f428021101e9a06c699 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 16:54:38 -0700 Subject: [PATCH 0345/1215] fix(bitwise.ts): add input length validation in the not() function to ensure it is a positive value --- src/lib/gadgets/bitwise.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 8ffdb45c09..0e94aa46e9 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -6,7 +6,9 @@ import * as Gates from '../gates.js'; export { xor, not }; function not(a: Field, length: number) { - + // check that input length is positive + assert(length > 0, `Input length needs to be positive values.`); + } function xor(a: Field, b: Field, length: number) { From 140df265202ad7fc5bb1fc054e550b775853b0c3 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 16:55:34 -0700 Subject: [PATCH 0346/1215] fix(bitwise.ts): add assertion to check that length does not exceed maximum field size in bits to prevent errors --- src/lib/gadgets/bitwise.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 0e94aa46e9..495307ef15 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -9,6 +9,12 @@ function not(a: Field, length: number) { // check that input length is positive assert(length > 0, `Input length needs to be positive values.`); + // Check that length does not exceed maximum field size in bits + assert( + length <= Field.sizeInBits(), + `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + ); + } function xor(a: Field, b: Field, length: number) { From 8a3efb5983defbc95f4c211d2e826dd640f0c10c Mon Sep 17 00:00:00 2001 From: ymekuria Date: Tue, 24 Oct 2023 17:25:21 -0700 Subject: [PATCH 0347/1215] fix(bitwise.ts): handle constant case in the not() function to correctly perform bitwise NOT operation on constant values --- src/lib/gadgets/bitwise.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 495307ef15..9656e3d907 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -15,6 +15,16 @@ function not(a: Field, length: number) { `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); + // obtain pad length until the length is a multiple of 16 for n-bit length lookup table + let padLength = Math.ceil(length / 16) * 16; + + // Handle constant case + if (a.isConstant()) { + let max = 1n << BigInt(padLength); + assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); + return new Field(Fp.not(a.toBigInt())); + } + } function xor(a: Field, b: Field, length: number) { From 930bdc748419d94873c23b0bb48bf09d68f5e383 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 00:29:19 -0700 Subject: [PATCH 0348/1215] feat(bitwise.ts): create a bitmask with all ones in not function --- src/lib/gadgets/bitwise.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 9656e3d907..e0b008cc13 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -18,13 +18,18 @@ function not(a: Field, length: number) { // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; + // Create a bitmask with all ones + let allOnes = BigInt(2 ** length - 1); + // Handle constant case if (a.isConstant()) { let max = 1n << BigInt(padLength); assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); - return new Field(Fp.not(a.toBigInt())); + return new Field(Fp.(a.toBigInt())); } + + } function xor(a: Field, b: Field, length: number) { From 78f6659384dbc1148696908841cea429ea7817b3 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 00:51:22 -0700 Subject: [PATCH 0349/1215] refactor(bitwise.ts): refactor not() function to use xor() function --- src/lib/gadgets/bitwise.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index e0b008cc13..d46e709505 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -19,17 +19,20 @@ function not(a: Field, length: number) { let padLength = Math.ceil(length / 16) * 16; // Create a bitmask with all ones - let allOnes = BigInt(2 ** length - 1); + let allOnes = new Field(BigInt(2 ** length - 1)); + + let notOutput = xor(a, allOnes, length); + // Handle constant case if (a.isConstant()) { let max = 1n << BigInt(padLength); assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); - return new Field(Fp.(a.toBigInt())); + return new Field(Fp.not(a.toBigInt())); } - - + return notOutput; + } function xor(a: Field, b: Field, length: number) { From 5df6fb442334d26f2b1dbd3761abb6da51c4ed19 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 01:14:55 -0700 Subject: [PATCH 0350/1215] feat(bitwise.unit-test.ts): add unit test for the `Not` gadget in the `Bitwise` module The `Not` gadget is a new addition to the `Bitwise` module. This commit adds a unit test for the `Not` gadget in the `bitwise.unit-test.ts` file. The unit test verifies the correctness of the `Not` gadget by running it with a private input and asserting the expected output. --- src/lib/gadgets/bitwise.unit-test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4b92ce7aff..f121a13623 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -11,6 +11,18 @@ import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; +let Not = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(a) { + Gadgets.not(a, 64); + }, + }, + }, +}); + + let Bitwise = ZkProgram({ publicOutput: Field, methods: { From 20ffa9d1f114fa7ebc433f47460923857553c428 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 01:39:21 -0700 Subject: [PATCH 0351/1215] feat(bitwise.unit-test.ts): add test cases for the 'not' operation on different lengths of inputs --- src/lib/gadgets/bitwise.unit-test.ts | 35 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f121a13623..398eaa67d9 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -11,18 +11,6 @@ import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; -let Not = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(a) { - Gadgets.not(a, 64); - }, - }, - }, -}); - - let Bitwise = ZkProgram({ publicOutput: Field, methods: { @@ -68,3 +56,26 @@ await equivalentAsync( return proof.publicOutput; } ); + +let NOT = ZkProgram({ + methods: { + run: { + privateInputs: [Field], + method(a) { + Gadgets.not(a, 64); + }, + }, + }, +}); + +await NOT.compile(); + +// not +[2, 4, 8, 16, 32, 64, 128].forEach((length) => { + equivalent({ from: [uint(length), uint(length)], to: field })( + Fp.not, + (x, ) => Gadgets.not(x, length) + ); +}); + + From 0085eac5ecd1a524d0a8e262667705e4e27d0fdd Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 01:46:10 -0700 Subject: [PATCH 0352/1215] test(bitwise.unit-test.ts): add unit test for NOT operation with equivalentAsync The unit test was added to test the NOT operation in the `bitwise.unit-test.ts` file. The test checks if the input value `x` is greater than or equal to 2^64 and throws an error if it does not fit into 64 bits. It then returns the result of the NOT operation on `x`. The test also includes an async function that uses the `Bitwise.not` method to calculate the NOT operation and returns the public output of the proof. --- src/lib/gadgets/bitwise.unit-test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 398eaa67d9..03caf032ec 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -79,3 +79,19 @@ await NOT.compile(); }); +await equivalentAsync( + { from: [maybeUint64], to: field }, + { runs: 3 } +)( + (x) => { + if (x >= 2n ** 64n) + throw Error('Does not fit into 64 bits'); + return Fp.not(x); + }, + async (x) => { + let proof = await Bitwise.not(x); + return proof.publicOutput; + } +); + + From d01ab48cb5c85154f19abcaf7ec5806bba98b4e7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 25 Oct 2023 12:18:46 +0200 Subject: [PATCH 0353/1215] helpers on mlarray --- src/lib/ml/base.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 10243efab7..87c5e40da5 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -38,6 +38,12 @@ const MlArray = { map([, ...arr]: MlArray, map: (t: T) => S): MlArray { return [0, ...arr.map(map)]; }, + mapTo(arr: T[], map: (t: T) => S): MlArray { + return [0, ...arr.map(map)]; + }, + mapFrom([, ...arr]: MlArray, map: (t: T) => S): S[] { + return arr.map(map); + }, }; const MlTuple = Object.assign( From 94221589e5337c2b101c17a87369694ff49240ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 25 Oct 2023 12:19:21 +0200 Subject: [PATCH 0354/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index e77f9f4db7..1ee39430d5 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e77f9f4db784d6ef8c7a7836fcc4fa43444f9560 +Subproject commit 1ee39430d5e1209fe1a0888604cfb8ab44317375 From 084a491dc717cb9515be61e442011aaeb4b3642c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 25 Oct 2023 12:19:29 +0200 Subject: [PATCH 0355/1215] enable srs caching --- src/lib/proof_system.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 337f2aebc3..a5627c34fe 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -34,6 +34,7 @@ import { encodeProverKey, proverKeyType, } from './proof-system/prover-keys.js'; +import { setSrsCache, unsetSrsCache } from '../bindings/crypto/bindings/srs.js'; // public API export { @@ -560,7 +561,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - storable: { read, write }, + storable, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -585,12 +586,12 @@ async function compileProgram({ let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; - let storable: Pickles.Storable = [ + let picklesStorable: Pickles.Storable = [ 0, function read_(key, path) { try { let type = proverKeyType(key); - let bytes = read(path, type); + let bytes = storable.read(path, type); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { return MlResult.unitError(); @@ -600,7 +601,7 @@ async function compileProgram({ try { let type = proverKeyType(key); let bytes = encodeProverKey(value); - write(path, bytes, type); + storable.write(path, bytes, type); return MlResult.ok(undefined); } catch (e: any) { return MlResult.unitError(); @@ -614,15 +615,17 @@ async function compileProgram({ withThreadPool(async () => { let result: ReturnType; let id = snarkContext.enter({ inCompile: true }); + setSrsCache(storable); try { result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), - storable, + storable: picklesStorable, overrideWrapDomain, }); } finally { snarkContext.leave(id); + unsetSrsCache(); } let { getVerificationKey, provers, verify, tag } = result; CompiledTag.store(proofSystemTag, tag); From 162732e51e38fdec1cc25f461c4b53b97a700907 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 25 Oct 2023 13:41:08 +0200 Subject: [PATCH 0356/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1ee39430d5..edefd67506 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1ee39430d5e1209fe1a0888604cfb8ab44317375 +Subproject commit edefd675062db3dff6cdfc4932a5cdc5a578b6a7 From 2f48759a93bad1a12f74ebe3f705a6e8c5b69030 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 11:59:58 -0700 Subject: [PATCH 0357/1215] feat(primitive_constraint_system.ts): add support for 'not' operation in the primitive constraint system example The 'not' operation has been added to the primitive constraint system. This operation allows for negating a value in the system. The 'not' operation is supported for 16-bit, 32-bit, 48-bit, and 64-bit values. --- src/examples/primitive_constraint_system.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 1ef5a5f87c..d15d2e3416 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -72,6 +72,14 @@ const BitwiseMock = { Gadgets.xor(a, b, 48); Gadgets.xor(a, b, 64); }, + + not() { + let a = Provable.witness(Field, () => new Field(5n)); + Gadgets.not(a, 16); + Gadgets.not(a, 32); + Gadgets.not(a, 48); + Gadgets.not(a, 64); + }, }; export const GroupCS = mock(GroupMock, 'Group Primitive'); From 417bc5de85373190e04653cab4be1a158e82177b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 12:10:21 -0700 Subject: [PATCH 0358/1215] refactor(bitwise.unit-test.ts): clean up test --- src/lib/gadgets/bitwise.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 03caf032ec..296f5f62b7 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -74,7 +74,7 @@ await NOT.compile(); [2, 4, 8, 16, 32, 64, 128].forEach((length) => { equivalent({ from: [uint(length), uint(length)], to: field })( Fp.not, - (x, ) => Gadgets.not(x, length) + (x) => Gadgets.not(x, length) ); }); From 358105d09931199e14e58c7176e18db2b86f361e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:12:06 -0700 Subject: [PATCH 0359/1215] chore(bindings): update subproject commit hash to fcda5090bc6bf433188c5152253d8370c25685c6 for latest changes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 8ee9bde95e..fcda5090bc 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 8ee9bde95e0ad6947eb303e4346f147deba10ed2 +Subproject commit fcda5090bc6bf433188c5152253d8370c25685c6 From 87d56012445ab9dfc46741ec8562195e044381e5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:14:12 -0700 Subject: [PATCH 0360/1215] feat(gadgets.ts): update doc comment Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index dc834c2218..8f44170c78 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -39,7 +39,10 @@ const Gadgets = { * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. * - * **Note:** You can not rotate {@link Field} elements that exceed 64 bits. For elements that exceed 64 bits this operation will fail. + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * Therefore, to safely use `rot()`, you need to make sure that the values passed in are range checked to 64 bits. + * For example, this can be done with {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to rotate. * @param bits amount of bits to rotate this {@link Field} element with. From 33aff5c6848aa2421b27a6bf21c70137271e8954 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:15:06 -0700 Subject: [PATCH 0361/1215] refactor(rot.ts): remove direction check in rot function --- src/lib/gadgets/rot.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 565d616be7..913d1dcbfc 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -13,12 +13,6 @@ function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); } - if (direction !== 'left' && direction !== 'right') { - throw Error( - `rot: expected direction to be 'left' or 'right', got ${direction}` - ); - } - if (field.isConstant()) { checkMaxBits(field); return new Field(Fp.rot(field.toBigInt(), bits, direction)); From 00530afef51180d9e10fac8a619c7c6c478597d3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:22:08 -0700 Subject: [PATCH 0362/1215] refactor(rot.ts, gates.ts): replace Field type with bigint for two_to_rot variable --- src/lib/gadgets/rot.ts | 2 +- src/lib/gates.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 913d1dcbfc..d28d23af5a 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -77,7 +77,7 @@ function rotate( witnessSlices(bound, 2, 2), // bits 2-4 witnessSlices(bound, 0, 2), // bits 0-2 ], - Field.from(big2PowerRot) + big2PowerRot ); // Compute next row Gates.rangeCheck64(shifted); diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 30701da882..bab62f0bf2 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -49,7 +49,7 @@ function rot( excess: Field, limbs: [Field, Field, Field, Field], crumbs: [Field, Field, Field, Field, Field, Field, Field, Field], - two_to_rot: Field + two_to_rot: bigint ) { Snarky.gates.rot( field.value, @@ -57,7 +57,7 @@ function rot( excess.value, MlArray.to(limbs.map((x) => x.value)), MlArray.to(crumbs.map((x) => x.value)), - FieldConst.fromBigint(two_to_rot.toBigInt()) + FieldConst.fromBigint(two_to_rot) ); } From 1948ea41cc9ba1814a3b1874f854668b471672ff Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:25:24 -0700 Subject: [PATCH 0363/1215] fix(rot.ts): remove unnecessary range check on 'field' variable --- src/lib/gadgets/rot.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index d28d23af5a..feead8c7ad 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -83,7 +83,6 @@ function rotate( Gates.rangeCheck64(shifted); // Compute following row Gates.rangeCheck64(excess); - Gates.rangeCheck64(field); return [rotated, excess, shifted]; } From c188a56a50ac573c3580e6d475dc6fb00f74ca59 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:32:36 -0700 Subject: [PATCH 0364/1215] refactor(gadgets.ts): change numeric literals to binary --- src/lib/gadgets/gadgets.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8f44170c78..f3a819d581 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -52,11 +52,11 @@ const Gadgets = { * * @example * ```ts - * const x = Provable.witness(Field, () => Field(12)); + * const x = Provable.witness(Field, () => Field(0b001100)); * const y = rot(x, 2, 'left'); // left rotation by 2 bits * const z = rot(x, 2, 'right'); // right rotation by 2 bits - * y.assertEquals(48); - * z.assertEquals(3) + * y.assertEquals(0b110000); + * z.assertEquals(0b000011) * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits From 8a98da9ba385371526c6a8214d8baeac05d9f8f7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:34:05 -0700 Subject: [PATCH 0365/1215] docs(CHANGELOG.md): update method description for bitwise rotation operation to provide more clarity on its functionality --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8287c5a4fc..96eccd1953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 -- Added bitwise `ROT` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1182 +- `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182 - `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. From 1f9fb240d3a848729760efc8b425b6430a3f9e4a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:42:59 -0700 Subject: [PATCH 0366/1215] feat(gadgets): rename rot to rotate --- src/bindings | 2 +- src/lib/gadgets/gadgets.ts | 6 +++--- src/lib/gadgets/gadgets.unit-test.ts | 8 ++++---- src/lib/gadgets/rot.ts | 14 +++++++++----- src/lib/gates.ts | 6 +++--- src/snarky.d.ts | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/bindings b/src/bindings index fcda5090bc..5e5befc857 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit fcda5090bc6bf433188c5152253d8370c25685c6 +Subproject commit 5e5befc8579393dadb96be1917642f860624ed07 diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f3a819d581..9f781507d9 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { rot } from './rot.js'; +import { rotate } from './rot.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -62,7 +62,7 @@ const Gadgets = { * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits * ``` */ - rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { - return rot(field, bits, direction); + rotate(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rotate(field, bits, direction); }, }; diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index cfd91adedd..bb03a3ae1f 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -57,8 +57,8 @@ let ROT = ZkProgram({ run: { privateInputs: [Field], method(x) { - Gadgets.rot(x, 2, 'left'); - Gadgets.rot(x, 2, 'right'); + Gadgets.rotate(x, 2, 'left'); + Gadgets.rotate(x, 2, 'right'); }, }, }, @@ -85,7 +85,7 @@ function testRot( Provable.runAndCheck(() => { let w = Provable.witness(Field, () => field); let r = Provable.witness(Field, () => result); - let output = Gadgets.rot(w, bits, mode); + let output = Gadgets.rotate(w, bits, mode); output.assertEquals(r, `rot(${field}, ${bits}, ${mode})`); }); } @@ -111,7 +111,7 @@ test( let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); Provable.runAndCheck(() => { let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rot(f, n, direction ? 'left' : 'right'); + let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); Provable.asProver(() => assert(r1 === r2.toBigInt())); }); } diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index feead8c7ad..fdad9f271b 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -3,11 +3,15 @@ import { Provable } from '../provable.js'; import { Fp } from '../../bindings/crypto/finite_field.js'; import * as Gates from '../gates.js'; -export { rot, rotate }; +export { rotate, rot }; const MAX_BITS = 64 as const; -function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { +function rotate( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { // Check that the rotation bits are in range if (bits < 0 || bits > MAX_BITS) { throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); @@ -17,11 +21,11 @@ function rot(field: Field, bits: number, direction: 'left' | 'right' = 'left') { checkMaxBits(field); return new Field(Fp.rot(field.toBigInt(), bits, direction)); } - const [rotated] = rotate(field, bits, direction); + const [rotated] = rot(field, bits, direction); return rotated; } -function rotate( +function rot( field: Field, bits: number, direction: 'left' | 'right' = 'left' @@ -57,7 +61,7 @@ function rotate( ); // Compute current row - Gates.rot( + Gates.rotate( field, rotated, excess, diff --git a/src/lib/gates.ts b/src/lib/gates.ts index bab62f0bf2..a82c500744 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -2,7 +2,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; import { MlArray } from './ml/base.js'; -export { rangeCheck64, rot }; +export { rangeCheck64, rotate }; /** * Asserts that x is at most 64 bits @@ -43,7 +43,7 @@ function rangeCheck64(x: Field) { ); } -function rot( +function rotate( field: Field, rotated: Field, excess: Field, @@ -51,7 +51,7 @@ function rot( crumbs: [Field, Field, Field, Field, Field, Field, Field, Field], two_to_rot: bigint ) { - Snarky.gates.rot( + Snarky.gates.rotate( field.value, rotated.value, excess.value, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 3bee503990..b89327d138 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -307,7 +307,7 @@ declare const Snarky: { compact: FieldConst ): void; - rot( + rotate( field: FieldVar, rotated: FieldVar, excess: FieldVar, From b3959506db7be28bbf4952484cb93e3042f77286 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:50:33 -0700 Subject: [PATCH 0367/1215] refactor(rot.ts): simplify comments for better readability --- src/lib/gadgets/rot.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index fdad9f271b..6afce0e4f0 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -51,10 +51,9 @@ function rot( big2Power64 ); - // Compute rotated value as: - // rotated = excess + shifted + // Compute rotated value as: rotated = excess + shifted const rotated = shifted + excess; - // Compute bound that is the right input of FFAdd equation + // Compute bound to check excess < 2^rot const bound = excess + big2Power64 - big2PowerRot; return [rotated, excess, shifted, bound].map(Field.from); } From 142e30f47c34352cfb3fe88853de8321911410f3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:52:19 -0700 Subject: [PATCH 0368/1215] fix(rot.ts): adjust validation range for rotation bits to exclude 0 and MAX_BITS --- src/lib/gadgets/rot.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 6afce0e4f0..79e72e961a 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -13,8 +13,10 @@ function rotate( direction: 'left' | 'right' = 'left' ) { // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); + if (bits <= 0 || bits >= MAX_BITS) { + throw Error( + `rot: expected bits to be in range [1, ${MAX_BITS - 1}], got ${bits}` + ); } if (field.isConstant()) { From b9ef95e90edf8959f7af0df1d0e2ed506a4a08ce Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:54:08 -0700 Subject: [PATCH 0369/1215] refactor(gadgets.unit-test.ts): simplify testRot function by removing unnecessary witness calls --- src/lib/gadgets/gadgets.unit-test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts index bb03a3ae1f..e956765a1a 100644 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ b/src/lib/gadgets/gadgets.unit-test.ts @@ -83,10 +83,8 @@ function testRot( result: Field ) { Provable.runAndCheck(() => { - let w = Provable.witness(Field, () => field); - let r = Provable.witness(Field, () => result); - let output = Gadgets.rotate(w, bits, mode); - output.assertEquals(r, `rot(${field}, ${bits}, ${mode})`); + let output = Gadgets.rotate(field, bits, mode); + output.assertEquals(result, `rot(${field}, ${bits}, ${mode})`); }); } From fbe8aa59fb2c41e1713924298a5c296b68249a93 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 25 Oct 2023 12:56:32 -0700 Subject: [PATCH 0370/1215] docs(gadgets.ts): enhance explanation of rotation operation and its constraints --- src/lib/gadgets/gadgets.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 9f781507d9..b3e37620fd 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -36,12 +36,15 @@ const Gadgets = { }, /** - * A (left and right) rotation is similar to the shift operation, `<<` and `>>` in JavaScript, just that bits are being appended to the other side. - * `direction` is a string which accepts either `'left'` or `'right'`, defining the direction of the rotation. + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, with the distinction that the bits are circulated to the opposite end rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * It’s important to note that these operations are performed considering the binary representation of the number in big-endian format, where the most significant bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. * * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. - * Therefore, to safely use `rot()`, you need to make sure that the values passed in are range checked to 64 bits. + * Therefore, to safely use `rotate()`, you need to make sure that the values passed in are range checked to 64 bits. * For example, this can be done with {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to rotate. From 8ee06b8c6dcaa76f2a6a328789d92c7a82b2d71c Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 14:40:43 -0700 Subject: [PATCH 0371/1215] chore(bindings): update subproject commit hash to d2dca1ccf95d37d3bd016ec0aa282b743b1b4173 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index dbe878db43..d2dca1ccf9 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit dbe878db43d256ac3085f248551b05b75ffecfda +Subproject commit d2dca1ccf95d37d3bd016ec0aa282b743b1b4173 From b25518bc666964c87a034670739f561cc182f296 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 15:35:53 -0700 Subject: [PATCH 0372/1215] chore(bindings): update subproject commit hash to 97f7017 for consistency and tracking purposes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index d2dca1ccf9..97f701708e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d2dca1ccf95d37d3bd016ec0aa282b743b1b4173 +Subproject commit 97f701708e20f63e1d140ec11375e8c5276734bc From 1dfd99054bdd81785da958431d3bf4d1dbafe4a7 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 16:03:13 -0700 Subject: [PATCH 0373/1215] feat(gadgets.ts): update NOT operation in Gadgets namespace --- src/lib/gadgets/gadgets.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0da2b8513a..d68c0c0868 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { xor } from './bitwise.js'; +import { xor, not } from './bitwise.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -82,8 +82,6 @@ const Gadgets = { * @param length - The number of bits to be considered for the NOT operation. */ not(a: Field, length: number) { - // mask with all bits set to 1, up to the specified length - const allOnes = Field((1n << BigInt(length)) - 1n); - return xor(a, allOnes, length); + return not(a, length); }, }; From 525651d7840eeff6b981f3f4cd6e12194d1655a1 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 16:06:28 -0700 Subject: [PATCH 0374/1215] feat(bitwise.ts): handle constant case before computation --- src/lib/gadgets/bitwise.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index d46e709505..d2536d70d0 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -17,6 +17,14 @@ function not(a: Field, length: number) { // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; + // Handle constant case + if (a.isConstant()) { + let max = 1n << BigInt(padLength); + assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); + return new Field(Fp.not(a.toBigInt())); + } + + // Create a bitmask with all ones let allOnes = new Field(BigInt(2 ** length - 1)); @@ -24,12 +32,7 @@ function not(a: Field, length: number) { let notOutput = xor(a, allOnes, length); - // Handle constant case - if (a.isConstant()) { - let max = 1n << BigInt(padLength); - assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); - return new Field(Fp.not(a.toBigInt())); - } + return notOutput; From c2b0930f5c3dfc771a31eced928a7949fc96e6e8 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 25 Oct 2023 16:55:46 -0700 Subject: [PATCH 0375/1215] chore(bindings): update subproject commit hash in bindings submodule --- src/bindings | 2 +- src/lib/gadgets/bitwise.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bindings b/src/bindings index 97f701708e..ac02f402f4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 97f701708e20f63e1d140ec11375e8c5276734bc +Subproject commit ac02f402f4d4d355919d3cbdf7812b60d24f2518 diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index d2536d70d0..32e20935aa 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -24,17 +24,15 @@ function not(a: Field, length: number) { return new Field(Fp.not(a.toBigInt())); } - - // Create a bitmask with all ones let allOnes = new Field(BigInt(2 ** length - 1)); let notOutput = xor(a, allOnes, length); + return notOutput; - return notOutput; } From 7114210a2f32c9eb77a6620eff4a3b9ba5b70680 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 26 Oct 2023 16:33:08 +0200 Subject: [PATCH 0376/1215] add name as a required argument to zkprogram (but optional in experimental version) --- src/index.ts | 5 ++--- src/lib/proof_system.ts | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6073135ff9..3aa161e08c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,7 +76,7 @@ export { MerkleMap, MerkleMapWitness } from './lib/merkle_map.js'; export { Nullifier } from './lib/nullifier.js'; -import { ZkProgram } from './lib/proof_system.js'; +import { ExperimentalZkProgram, ZkProgram } from './lib/proof_system.js'; export { ZkProgram }; // experimental APIs @@ -89,7 +89,6 @@ const Experimental_ = { Callback, createChildAccountUpdate, memoizeWitness, - ZkProgram, }; type Callback_ = Callback; @@ -102,7 +101,7 @@ namespace Experimental { /** @deprecated `ZkProgram` has moved out of the Experimental namespace and is now directly available as a top-level import `ZkProgram`. * The old `Experimental.ZkProgram` API has been deprecated in favor of the new `ZkProgram` top-level import. */ - export let ZkProgram = Experimental_.ZkProgram; + export let ZkProgram = ExperimentalZkProgram; export let createChildAccountUpdate = Experimental_.createChildAccountUpdate; export let memoizeWitness = Experimental_.memoizeWitness; export let Callback = Experimental_.Callback; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 6d3987bde3..7c1ca6a0f1 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -35,6 +35,7 @@ export { SelfProof, JsonProof, ZkProgram, + ExperimentalZkProgram, verify, Empty, Undefined, @@ -240,6 +241,7 @@ function ZkProgram< } >( config: StatementType & { + name: string; methods: { [I in keyof Types]: Method< InferProvableOrUndefined>, @@ -273,7 +275,7 @@ function ZkProgram< let publicInputType: ProvablePure = config.publicInput! ?? Undefined; let publicOutputType: ProvablePure = config.publicOutput! ?? Void; - let selfTag = { name: `Program${i++}` }; + let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< Get >; @@ -1021,3 +1023,30 @@ type UnwrapPromise

= P extends Promise ? T : never; type Get = T extends { [K in Key]: infer Value } ? Value : undefined; + +// deprecated experimental API + +function ExperimentalZkProgram< + StatementType extends { + publicInput?: FlexibleProvablePure; + publicOutput?: FlexibleProvablePure; + }, + Types extends { + [I in string]: Tuple; + } +>( + config: StatementType & { + name?: string; + methods: { + [I in keyof Types]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Types[I] + >; + }; + overrideWrapDomain?: 0 | 1 | 2; + } +) { + let config_ = { ...config, name: config.name ?? `Program${i++}` }; + return ZkProgram(config_); +} From 44f57ce9b39d342ad72cc140f9d52b0927505084 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 26 Oct 2023 16:43:51 +0200 Subject: [PATCH 0377/1215] add name argument to all zkprograms --- src/examples/benchmarks/mul-web.ts | 4 ++-- src/examples/benchmarks/mul.ts | 4 ++-- src/examples/ex02_root_program.ts | 5 ++--- src/examples/gadgets.ts | 5 +++-- src/examples/program-with-input.ts | 1 + src/examples/program.ts | 1 + src/lib/gadgets/bitwise.unit-test.ts | 1 + src/lib/gadgets/range-check.unit-test.ts | 1 + src/lib/proof_system.unit-test.ts | 2 ++ src/mina-signer/tests/verify-in-snark.unit-test.ts | 1 + src/tests/inductive-proofs-small.ts | 1 + src/tests/inductive-proofs.ts | 3 +++ tests/integration/inductive-proofs.js | 3 +++ 13 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/examples/benchmarks/mul-web.ts b/src/examples/benchmarks/mul-web.ts index d3b69f7575..e2b39ff9c8 100644 --- a/src/examples/benchmarks/mul-web.ts +++ b/src/examples/benchmarks/mul-web.ts @@ -1,9 +1,8 @@ /** * benchmark a circuit filled with generic gates */ -import { Circuit, Field, Provable, circuitMain, Experimental } from 'o1js'; +import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; import { tic, toc } from './tic-toc.js'; -let { ZkProgram } = Experimental; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows @@ -39,6 +38,7 @@ function simpleKimchiCircuit(nMuls: number) { function picklesCircuit(nMuls: number) { return ZkProgram({ + name: 'mul-chain', methods: { run: { privateInputs: [], diff --git a/src/examples/benchmarks/mul.ts b/src/examples/benchmarks/mul.ts index a4fe3ac742..deb9aabe43 100644 --- a/src/examples/benchmarks/mul.ts +++ b/src/examples/benchmarks/mul.ts @@ -1,9 +1,8 @@ /** * benchmark a circuit filled with generic gates */ -import { Circuit, Field, Provable, circuitMain, Experimental } from 'o1js'; +import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; import { tic, toc } from '../zkapps/tictoc.js'; -let { ZkProgram } = Experimental; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows @@ -37,6 +36,7 @@ function simpleKimchiCircuit(nMuls: number) { function picklesCircuit(nMuls: number) { return ZkProgram({ + name: 'mul-chain', methods: { run: { privateInputs: [], diff --git a/src/examples/ex02_root_program.ts b/src/examples/ex02_root_program.ts index b301a9f72f..1127c1d81f 100644 --- a/src/examples/ex02_root_program.ts +++ b/src/examples/ex02_root_program.ts @@ -1,8 +1,7 @@ -import { Field, UInt64, Experimental, Gadgets } from 'o1js'; - -let { ZkProgram } = Experimental; +import { Field, UInt64, Gadgets, ZkProgram } from 'o1js'; const Main = ZkProgram({ + name: 'example-with-custom-gates', publicInput: Field, methods: { main: { diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 2048b294fa..48113d8771 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,6 +1,7 @@ -import { Field, Provable, Gadgets, Experimental } from 'o1js'; +import { Field, Provable, Gadgets, ZkProgram } from 'o1js'; -const XOR = Experimental.ZkProgram({ +const XOR = ZkProgram({ + name: 'xor-example', methods: { baseCase: { privateInputs: [], diff --git a/src/examples/program-with-input.ts b/src/examples/program-with-input.ts index f506e61b07..005cce1b14 100644 --- a/src/examples/program-with-input.ts +++ b/src/examples/program-with-input.ts @@ -12,6 +12,7 @@ import { await isReady; let MyProgram = ZkProgram({ + name: 'example-with-input', publicInput: Field, methods: { diff --git a/src/examples/program.ts b/src/examples/program.ts index a60bbc54d5..40b5263854 100644 --- a/src/examples/program.ts +++ b/src/examples/program.ts @@ -13,6 +13,7 @@ import { await isReady; let MyProgram = ZkProgram({ + name: 'example-with-output', publicOutput: Field, methods: { diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4b92ce7aff..f546e90108 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -12,6 +12,7 @@ import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; let Bitwise = ZkProgram({ + name: 'bitwise', publicOutput: Field, methods: { xor: { diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 669f811174..f8cda9ead1 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -13,6 +13,7 @@ import { Gadgets } from './gadgets.js'; // TODO: make a ZkFunction or something that doesn't go through Pickles let RangeCheck64 = ZkProgram({ + name: 'range-check-64', methods: { run: { privateInputs: [Field], diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index e7ec592915..477d354114 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -5,6 +5,7 @@ import { ZkProgram } from './proof_system.js'; import { expect } from 'expect'; const EmptyProgram = ZkProgram({ + name: 'empty', publicInput: Field, methods: { run: { @@ -30,6 +31,7 @@ class CounterPublicInput extends Struct({ updated: UInt64, }) {} const CounterProgram = ZkProgram({ + name: 'counter', publicInput: CounterPublicInput, methods: { increment: { diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index 104fabad6c..c32180cff0 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -32,6 +32,7 @@ signature.verify(publicKey, fieldsSnarky).assertTrue(); const Message = Provable.Array(Field, fields.length); const MyProgram = ZkProgram({ + name: 'verify-signature', methods: { verifySignature: { privateInputs: [Signature, Message], diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index 5f6c063709..d24f741302 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -11,6 +11,7 @@ import { tic, toc } from '../examples/zkapps/tictoc.js'; await isReady; let MaxProofsVerifiedOne = ZkProgram({ + name: 'recursive-1', publicInput: Field, methods: { diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index 9accd6a4b5..7fa724d162 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -11,6 +11,7 @@ import { tic, toc } from '../examples/zkapps/tictoc.js'; await isReady; let MaxProofsVerifiedZero = ZkProgram({ + name: 'no-recursion', publicInput: Field, methods: { @@ -25,6 +26,7 @@ let MaxProofsVerifiedZero = ZkProgram({ }); let MaxProofsVerifiedOne = ZkProgram({ + name: 'recursive-1', publicInput: Field, methods: { @@ -48,6 +50,7 @@ let MaxProofsVerifiedOne = ZkProgram({ }); let MaxProofsVerifiedTwo = ZkProgram({ + name: 'recursive-2', publicInput: Field, methods: { diff --git a/tests/integration/inductive-proofs.js b/tests/integration/inductive-proofs.js index 9238d67117..d8ff85cfd0 100644 --- a/tests/integration/inductive-proofs.js +++ b/tests/integration/inductive-proofs.js @@ -10,6 +10,7 @@ import { tic, toc } from './tictoc.js'; await isReady; let MaxProofsVerifiedZero = ZkProgram({ + name: 'no-recursion', publicInput: Field, methods: { @@ -24,6 +25,7 @@ let MaxProofsVerifiedZero = ZkProgram({ }); let MaxProofsVerifiedOne = ZkProgram({ + name: 'recursive-1', publicInput: Field, methods: { @@ -47,6 +49,7 @@ let MaxProofsVerifiedOne = ZkProgram({ }); let MaxProofsVerifiedTwo = ZkProgram({ + name: 'recursive-2', publicInput: Field, methods: { From ef0d5e4497938cf9d3c8cb8eb179d58149e2bc25 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 26 Oct 2023 16:49:47 +0200 Subject: [PATCH 0378/1215] changelog --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3904cf0a1b..ba6b4605dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,17 +26,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - `ZkProgram` has moved out of the `Experimental` namespace and is now available as a top-level import directly. `Experimental.ZkProgram` has been deprecated. +- `ZkProgram` gets a new input argument `name: string` which is required in the non-experimental API. The name is used to identify a ZkProgram when caching prover keys. https://github.com/o1-labs/o1js/pull/1200 ### Added - `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 - - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 - - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 - - Added bitwise `XOR` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1177 - - `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. From 8c5079233f28aae3c4d40255867288a8cd5ed8d8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 08:38:30 -0700 Subject: [PATCH 0379/1215] Revert "fix(rot.ts): adjust validation range for rotation bits to exclude 0 and MAX_BITS" This reverts commit 142e30f47c34352cfb3fe88853de8321911410f3. --- src/lib/gadgets/rot.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 79e72e961a..6afce0e4f0 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -13,10 +13,8 @@ function rotate( direction: 'left' | 'right' = 'left' ) { // Check that the rotation bits are in range - if (bits <= 0 || bits >= MAX_BITS) { - throw Error( - `rot: expected bits to be in range [1, ${MAX_BITS - 1}], got ${bits}` - ); + if (bits < 0 || bits > MAX_BITS) { + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); } if (field.isConstant()) { From 36415ad666933783b8a3596dd196c75b31b68c69 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 08:40:01 -0700 Subject: [PATCH 0380/1215] refactor(rot.ts): remove redundant checkMaxBits function call to improve performance The checkMaxBits function was previously used to ensure that the input is at most 64 bits. However, this check is no longer necessary as the input size is now guaranteed by the type system. --- src/lib/gadgets/rot.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts index 6afce0e4f0..11a2b997dc 100644 --- a/src/lib/gadgets/rot.ts +++ b/src/lib/gadgets/rot.ts @@ -30,11 +30,6 @@ function rot( bits: number, direction: 'left' | 'right' = 'left' ): [Field, Field, Field] { - // Check as the prover, that the input is at most 64 bits. - Provable.asProver(() => { - checkMaxBits(field); - }); - const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; const big2Power64 = 2n ** BigInt(MAX_BITS); const big2PowerRot = 2n ** BigInt(rotationBits); From b07707807e76db51c51015b85215ceeb8df43616 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:04:00 -0700 Subject: [PATCH 0381/1215] fix(regression_test.json): update 'rot' method's rows and digest values to reflect recent changes --- src/examples/regression_test.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 182f3a4b0c..40e164b481 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -169,8 +169,8 @@ "digest": "Bitwise Primitive", "methods": { "rot": { - "rows": 17, - "digest": "916f4017a60f48d56d487c6869919b9c" + "rows": 13, + "digest": "2c0dadbba96fd7ddb9adb7d643425ce3" }, "xor": { "rows": 15, @@ -182,4 +182,4 @@ "hash": "" } } -} +} \ No newline at end of file From 9075a94b046ff121d76289dbb6124add5617b291 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:04:28 -0700 Subject: [PATCH 0382/1215] refactor(gadgets.ts): rename 'rot' function to 'rotate' --- src/examples/gadgets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index 109bb19529..85f9bb2d45 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -3,8 +3,8 @@ import { Field, Provable, Experimental, Gadgets } from 'o1js'; let cs = Provable.constraintSystem(() => { let f = Provable.witness(Field, () => Field(12)); - let res1 = Gadgets.rot(f, 2, 'left'); - let res2 = Gadgets.rot(f, 2, 'right'); + let res1 = Gadgets.rotate(f, 2, 'left'); + let res2 = Gadgets.rotate(f, 2, 'right'); res1.assertEquals(Field(48)); res2.assertEquals(Field(3)); From 03b4988c1bfe382bb9451899e6b3e17ce1974b98 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:05:05 -0700 Subject: [PATCH 0383/1215] docs(CHANGELOG.md): update description for XOR operation to match format of other entries for consistency --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7918685120..e2a8874328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182 -- Added bitwise `XOR` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1177 +- `Gadgets.xor()`, new provable method to support bitwise xor for native field elements. https://github.com/o1-labs/o1js/pull/1177 - `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. From e2ff9926e2edc00180437ff40a85897bff1f20b4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:08:02 -0700 Subject: [PATCH 0384/1215] refactor(bitwise.ts): merge rotate function from rot.ts into bitwise.ts --- src/lib/gadgets/bitwise.ts | 95 +++++++++++++++++++++++++++++++- src/lib/gadgets/gadgets.ts | 3 +- src/lib/gadgets/rot.ts | 109 ------------------------------------- 3 files changed, 95 insertions(+), 112 deletions(-) delete mode 100644 src/lib/gadgets/rot.ts diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 53e6c432a4..4a48ef2123 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -3,7 +3,9 @@ import { Field as Fp } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; import * as Gates from '../gates.js'; -export { xor }; +export { xor, rotate }; + +const MAX_BITS = 64 as const; function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive @@ -111,6 +113,83 @@ function buildXor( zero.assertEquals(expectedOutput); } +function rotate( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { + // Check that the rotation bits are in range + if (bits < 0 || bits > MAX_BITS) { + throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); + } + + if (field.isConstant()) { + checkMaxBits(field); + return new Field(Fp.rot(field.toBigInt(), bits, direction)); + } + const [rotated] = rot(field, bits, direction); + return rotated; +} + +function rot( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +): [Field, Field, Field] { + const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; + const big2Power64 = 2n ** BigInt(MAX_BITS); + const big2PowerRot = 2n ** BigInt(rotationBits); + + const [rotated, excess, shifted, bound] = Provable.witness( + Provable.Array(Field, 4), + () => { + const f = field.toBigInt(); + + // Obtain rotated output, excess, and shifted for the equation: + // f * 2^rot = excess * 2^64 + shifted + const { quotient: excess, remainder: shifted } = divideWithRemainder( + f * big2PowerRot, + big2Power64 + ); + + // Compute rotated value as: rotated = excess + shifted + const rotated = shifted + excess; + // Compute bound to check excess < 2^rot + const bound = excess + big2Power64 - big2PowerRot; + return [rotated, excess, shifted, bound].map(Field.from); + } + ); + + // Compute current row + Gates.rotate( + field, + rotated, + excess, + [ + witnessSlices(bound, 52, 12), // bits 52-64 + witnessSlices(bound, 40, 12), // bits 40-52 + witnessSlices(bound, 28, 12), // bits 28-40 + witnessSlices(bound, 16, 12), // bits 16-28 + ], + [ + witnessSlices(bound, 14, 2), // bits 14-16 + witnessSlices(bound, 12, 2), // bits 12-14 + witnessSlices(bound, 10, 2), // bits 10-12 + witnessSlices(bound, 8, 2), // bits 8-10 + witnessSlices(bound, 6, 2), // bits 6-8 + witnessSlices(bound, 4, 2), // bits 4-6 + witnessSlices(bound, 2, 2), // bits 2-4 + witnessSlices(bound, 0, 2), // bits 0-2 + ], + big2PowerRot + ); + // Compute next row + Gates.rangeCheck64(shifted); + // Compute following row + Gates.rangeCheck64(excess); + return [rotated, excess, shifted]; +} + function assert(stmt: boolean, message?: string) { if (!stmt) { throw Error(message ?? 'Assertion failed'); @@ -129,3 +208,17 @@ function witnessSlices(f: Field, start: number, length: number) { function witnessNextValue(current: Field) { return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); } + +function checkMaxBits(x: Field) { + if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { + throw Error( + `rot: expected field to be at most 64 bits, got ${x.toBigInt()}` + ); + } +} + +function divideWithRemainder(numerator: bigint, denominator: bigint) { + const quotient = numerator / denominator; + const remainder = numerator - denominator * quotient; + return { quotient, remainder }; +} diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 942f7bcccf..8234bb400c 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,8 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { rotate } from './rot.js'; -import { xor } from './bitwise.js'; +import { xor, rotate } from './bitwise.js'; import { Field } from '../core.js'; export { Gadgets }; diff --git a/src/lib/gadgets/rot.ts b/src/lib/gadgets/rot.ts deleted file mode 100644 index 11a2b997dc..0000000000 --- a/src/lib/gadgets/rot.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Field } from '../field.js'; -import { Provable } from '../provable.js'; -import { Fp } from '../../bindings/crypto/finite_field.js'; -import * as Gates from '../gates.js'; - -export { rotate, rot }; - -const MAX_BITS = 64 as const; - -function rotate( - field: Field, - bits: number, - direction: 'left' | 'right' = 'left' -) { - // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); - } - - if (field.isConstant()) { - checkMaxBits(field); - return new Field(Fp.rot(field.toBigInt(), bits, direction)); - } - const [rotated] = rot(field, bits, direction); - return rotated; -} - -function rot( - field: Field, - bits: number, - direction: 'left' | 'right' = 'left' -): [Field, Field, Field] { - const rotationBits = direction === 'right' ? MAX_BITS - bits : bits; - const big2Power64 = 2n ** BigInt(MAX_BITS); - const big2PowerRot = 2n ** BigInt(rotationBits); - - const [rotated, excess, shifted, bound] = Provable.witness( - Provable.Array(Field, 4), - () => { - const f = field.toBigInt(); - - // Obtain rotated output, excess, and shifted for the equation: - // f * 2^rot = excess * 2^64 + shifted - const { quotient: excess, remainder: shifted } = divideWithRemainder( - f * big2PowerRot, - big2Power64 - ); - - // Compute rotated value as: rotated = excess + shifted - const rotated = shifted + excess; - // Compute bound to check excess < 2^rot - const bound = excess + big2Power64 - big2PowerRot; - return [rotated, excess, shifted, bound].map(Field.from); - } - ); - - // Compute current row - Gates.rotate( - field, - rotated, - excess, - [ - witnessSlices(bound, 52, 12), // bits 52-64 - witnessSlices(bound, 40, 12), // bits 40-52 - witnessSlices(bound, 28, 12), // bits 28-40 - witnessSlices(bound, 16, 12), // bits 16-28 - ], - [ - witnessSlices(bound, 14, 2), // bits 14-16 - witnessSlices(bound, 12, 2), // bits 12-14 - witnessSlices(bound, 10, 2), // bits 10-12 - witnessSlices(bound, 8, 2), // bits 8-10 - witnessSlices(bound, 6, 2), // bits 6-8 - witnessSlices(bound, 4, 2), // bits 4-6 - witnessSlices(bound, 2, 2), // bits 2-4 - witnessSlices(bound, 0, 2), // bits 0-2 - ], - big2PowerRot - ); - // Compute next row - Gates.rangeCheck64(shifted); - // Compute following row - Gates.rangeCheck64(excess); - return [rotated, excess, shifted]; -} - -function checkMaxBits(x: Field) { - if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { - throw Error( - `rot: expected field to be at most 64 bits, got ${x.toBigInt()}` - ); - } -} - -// TODO: move to utils once https://github.com/o1-labs/o1js/pull/1177 is merged -function witnessSlices(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - return Provable.witness(Field, () => { - let mask = (1n << BigInt(length)) - 1n; - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & mask); - }); -} - -function divideWithRemainder(numerator: bigint, denominator: bigint) { - const quotient = numerator / denominator; - const remainder = numerator - denominator * quotient; - return { quotient, remainder }; -} From 6405b755d5126459e6eaa03cdf3ff321e1bf2a55 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:11:07 -0700 Subject: [PATCH 0385/1215] docs(gadgets.ts): enhance documentation for XOR gadget function - Change single line comment to JSDoc style for better IDE support - Add parameter descriptions for better understanding - Improve wording and formatting for better readability - Add @throws tag to highlight error conditions - Update example code to match new parameter descriptions --- src/lib/gadgets/gadgets.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8234bb400c..59e4a8e305 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -69,7 +69,7 @@ const Gadgets = { return rotate(field, bits, direction); }, - /* + /** * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. * @@ -78,17 +78,26 @@ const Gadgets = { * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * * **Note:** Specifying a larger `length` parameter adds additional constraints. + * * It is also important to mention that specifying a smaller `length` allows the verifier to infer the length of the original input data (e.g. smaller than 16 bit if only one XOR gate has been used). * A zkApp developer should consider these implications when choosing the `length` parameter and carefully weigh the trade-off between increased amount of constraints and security. * - * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated.. + * **Important:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. + * * For example, with `length = 2` (`paddedLength = 16`), `xor()` will fail for any input that is larger than `2**16`. * - * ```typescript - * let a = Field(5); // ... 000101 - * let b = Field(3); // ... 000011 + * @param a {@link Field} element to compare. + * @param b {@link Field} element to compare. + * @param length amount of bits to compare. + * + * @throws Throws an error if the input values exceed `2^paddedLength - 1`. + * + * @example + * ```ts + * let a = Field(5); // ... 000101 + * let b = Field(3); // ... 000011 * - * let c = xor(a, b, 2); // ... 000110 + * let c = xor(a, b, 2); // ... 000110 * c.assertEquals(6); * ``` */ From 131bce24e7e36d3758b987363d912bfd8e2eacb6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:16:39 -0700 Subject: [PATCH 0386/1215] refactor(bitwise.ts, common.ts): extract common functions and constants to a separate file --- src/lib/gadgets/bitwise.ts | 54 +++++++++++--------------------------- src/lib/gadgets/common.ts | 37 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 src/lib/gadgets/common.ts diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 4a48ef2123..70a6c9b6b1 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -2,11 +2,16 @@ import { Provable } from '../provable.js'; import { Field as Fp } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; import * as Gates from '../gates.js'; +import { + MAX_BITS, + assert, + witnessSlices, + witnessNextValue, + divideWithRemainder, +} from './common.js'; export { xor, rotate }; -const MAX_BITS = 64 as const; - function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); @@ -119,12 +124,16 @@ function rotate( direction: 'left' | 'right' = 'left' ) { // Check that the rotation bits are in range - if (bits < 0 || bits > MAX_BITS) { - throw Error(`rot: expected bits to be between 0 and 64, got ${bits}`); - } + assert( + bits > 0 && bits < MAX_BITS, + `rotation: expected bits to be between 0 and 64, got ${bits}` + ); if (field.isConstant()) { - checkMaxBits(field); + assert( + field.toBigInt() < 2n ** BigInt(MAX_BITS), + `rotation: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); return new Field(Fp.rot(field.toBigInt(), bits, direction)); } const [rotated] = rot(field, bits, direction); @@ -189,36 +198,3 @@ function rot( Gates.rangeCheck64(excess); return [rotated, excess, shifted]; } - -function assert(stmt: boolean, message?: string) { - if (!stmt) { - throw Error(message ?? 'Assertion failed'); - } -} - -function witnessSlices(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - - return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); - }); -} - -function witnessNextValue(current: Field) { - return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); -} - -function checkMaxBits(x: Field) { - if (x.toBigInt() > BigInt(2 ** MAX_BITS)) { - throw Error( - `rot: expected field to be at most 64 bits, got ${x.toBigInt()}` - ); - } -} - -function divideWithRemainder(numerator: bigint, denominator: bigint) { - const quotient = numerator / denominator; - const remainder = numerator - denominator * quotient; - return { quotient, remainder }; -} diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts new file mode 100644 index 0000000000..cade7e3417 --- /dev/null +++ b/src/lib/gadgets/common.ts @@ -0,0 +1,37 @@ +import { Provable } from '../provable.js'; +import { Field } from '../field.js'; + +const MAX_BITS = 64 as const; + +export { + MAX_BITS, + assert, + witnessSlices, + witnessNextValue, + divideWithRemainder, +}; + +function assert(stmt: boolean, message?: string) { + if (!stmt) { + throw Error(message ?? 'Assertion failed'); + } +} + +function witnessSlices(f: Field, start: number, length: number) { + if (length <= 0) throw Error('Length must be a positive number'); + + return Provable.witness(Field, () => { + let n = f.toBigInt(); + return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); + }); +} + +function witnessNextValue(current: Field) { + return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); +} + +function divideWithRemainder(numerator: bigint, denominator: bigint) { + const quotient = numerator / denominator; + const remainder = numerator - denominator * quotient; + return { quotient, remainder }; +} From eeabe7ceae3431a138a3273df2b46e5614c817a1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:23:20 -0700 Subject: [PATCH 0387/1215] feat(range-check.unit-test.ts): add name property to ROT ZkProgram --- src/lib/gadgets/range-check.unit-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index fc812a6454..88a991e6ae 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -54,6 +54,7 @@ await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( // ROT Gate // -------------------------- let ROT = ZkProgram({ + name: 'rot', methods: { run: { privateInputs: [Field], From 6f2a0dd71298bb514c713b1138ff779773a6ca57 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:24:06 -0700 Subject: [PATCH 0388/1215] fix(range-check.unit-test.ts): change Field value from string to BigInt --- src/lib/gadgets/range-check.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 88a991e6ae..4d0d77fb69 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -93,7 +93,7 @@ function testRot( testRot(Field(0), 0, 'left', Field(0)); testRot(Field(0), 32, 'right', Field(0)); testRot(Field(1), 1, 'left', Field(2)); -testRot(Field(1), 63, 'left', Field('9223372036854775808')); +testRot(Field(1), 63, 'left', Field(9223372036854775808n)); testRot(Field(256), 4, 'right', Field(16)); testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); From e48c5e4d860e9156eaa22bfb69131ed9393b0466 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:42:35 -0700 Subject: [PATCH 0389/1215] fix(bitwise.ts): adjust rotation bits range check to include 0 and MAX_BITS --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 70a6c9b6b1..b51f90a801 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -125,7 +125,7 @@ function rotate( ) { // Check that the rotation bits are in range assert( - bits > 0 && bits < MAX_BITS, + bits >= 0 && bits <= MAX_BITS, `rotation: expected bits to be between 0 and 64, got ${bits}` ); From 7cd98ee22d90dac15f012ede76a60b4633607554 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 09:45:32 -0700 Subject: [PATCH 0390/1215] refactor(bitwise.unit-test.ts): move rot tests from range-check.unit-test.ts to bitwise.unit-test.ts --- src/lib/gadgets/bitwise.unit-test.ts | 61 +++++++++++++++++++- src/lib/gadgets/range-check.unit-test.ts | 71 +----------------------- 2 files changed, 59 insertions(+), 73 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f546e90108..f395b9ebb6 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -7,9 +7,10 @@ import { fieldWithRng, } from '../testing/equivalent.js'; import { Fp, mod } from '../../bindings/crypto/finite_field.js'; -import { Field } from '../field.js'; +import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; -import { Random } from '../testing/property.js'; +import { test, Random } from '../testing/property.js'; +import { Provable } from '../provable.js'; let Bitwise = ZkProgram({ name: 'bitwise', @@ -21,6 +22,12 @@ let Bitwise = ZkProgram({ return Gadgets.xor(a, b, 64); }, }, + rot: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.rotate(a, 12, 'left'); + }, + }, }, }); @@ -35,6 +42,21 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); ); }); +test( + Random.uint64, + Random.nat(64), + Random.boolean, + (x, n, direction, assert) => { + let z = Field(x); + let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + } +); + let maybeUint64: Spec = { ...field, rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => @@ -42,7 +64,6 @@ let maybeUint64: Spec = { ), }; -// do a couple of proofs await equivalentAsync( { from: [maybeUint64, maybeUint64], to: field }, { runs: 3 } @@ -57,3 +78,37 @@ await equivalentAsync( return proof.publicOutput; } ); + +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.rot(x, 12, 'left'); + }, + async (x) => { + let proof = await Bitwise.rot(x); + return proof.publicOutput; + } +); + +function testRot( + field: Field, + bits: number, + mode: 'left' | 'right', + result: Field +) { + Provable.runAndCheck(() => { + let output = Gadgets.rotate(field, bits, mode); + output.assertEquals(result, `rot(${field}, ${bits}, ${mode})`); + }); +} + +testRot(Field(0), 0, 'left', Field(0)); +testRot(Field(0), 32, 'right', Field(0)); +testRot(Field(1), 1, 'left', Field(2)); +testRot(Field(1), 63, 'left', Field(9223372036854775808n)); +testRot(Field(256), 4, 'right', Field(16)); +testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); +testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); +testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); +testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); +testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 4d0d77fb69..4466f5e187 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -7,10 +7,8 @@ import { equivalentAsync, field, } from '../testing/equivalent.js'; -import { test, Random } from '../testing/property.js'; -import { Field as Fp } from '../../provable/field-bigint.js'; +import { Random } from '../testing/property.js'; import { Gadgets } from './gadgets.js'; -import { Provable } from '../provable.js'; let maybeUint64: Spec = { ...field, @@ -49,70 +47,3 @@ await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( return await RangeCheck64.verify(proof); } ); - -// -------------------------- -// ROT Gate -// -------------------------- -let ROT = ZkProgram({ - name: 'rot', - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.rotate(x, 2, 'left'); - Gadgets.rotate(x, 2, 'right'); - }, - }, - }, -}); - -await ROT.compile(); -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( - (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); - return true; - }, - async (x) => { - let proof = await ROT.run(x); - return await ROT.verify(proof); - } -); - -function testRot( - field: Field, - bits: number, - mode: 'left' | 'right', - result: Field -) { - Provable.runAndCheck(() => { - let output = Gadgets.rotate(field, bits, mode); - output.assertEquals(result, `rot(${field}, ${bits}, ${mode})`); - }); -} - -testRot(Field(0), 0, 'left', Field(0)); -testRot(Field(0), 32, 'right', Field(0)); -testRot(Field(1), 1, 'left', Field(2)); -testRot(Field(1), 63, 'left', Field(9223372036854775808n)); -testRot(Field(256), 4, 'right', Field(16)); -testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); -testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); -testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); - -// rotation -test( - Random.uint64, - Random.nat(64), - Random.boolean, - (x, n, direction, assert) => { - let z = Field(x); - let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - } -); From 252b40c44da50cef5b2b15c9f852cd5e07a601a7 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 10:41:08 -0700 Subject: [PATCH 0391/1215] feat(bitwise.ts): use Provable.witness to create a witness for the allOnes bitmask --- src/lib/gadgets/bitwise.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 32e20935aa..bc54688599 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -17,23 +17,25 @@ function not(a: Field, length: number) { // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; - // Handle constant case + + // handle constant case if (a.isConstant()) { let max = 1n << BigInt(padLength); - assert(a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits`); + assert( + a.toBigInt() < max, + `${a.toBigInt()} does not fit into ${padLength} bits` + ); return new Field(Fp.not(a.toBigInt())); } - // Create a bitmask with all ones - let allOnes = new Field(BigInt(2 ** length - 1)); - - let notOutput = xor(a, allOnes, length); + let allOnes = Provable.witness(Field, () => { + // Create a bitmask with all ones + return new Field(BigInt(2 ** length - 1)); + }); + let notOutput = xor(a, allOnes, length); return notOutput; - - - } function xor(a: Field, b: Field, length: number) { From bf2e81adfe087e249242d5a55d721797e9068e88 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 11:04:28 -0700 Subject: [PATCH 0392/1215] feat(bitwise.ts): add assertion to allones bitmask --- src/lib/gadgets/bitwise.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index bc54688599..abfae89b48 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -28,11 +28,15 @@ function not(a: Field, length: number) { return new Field(Fp.not(a.toBigInt())); } + // create a bitmask with all ones + let allOnesF = new Field(BigInt(2 ** length - 1)); + let allOnes = Provable.witness(Field, () => { - // Create a bitmask with all ones - return new Field(BigInt(2 ** length - 1)); + return allOnesF; }); + allOnesF.assertEquals(allOnes); + let notOutput = xor(a, allOnes, length); return notOutput; From f9fb06fc0eb4ac08cd82507f555cf639a3a42299 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 11:48:59 -0700 Subject: [PATCH 0393/1215] test(bitwise.unit-test.ts): update equivalentAsync test to use the refactored not method in Bitwise ZkProgram --- src/lib/gadgets/bitwise.unit-test.ts | 39 +++++++++------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 296f5f62b7..d4e6be7f2f 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,6 +20,12 @@ let Bitwise = ZkProgram({ return Gadgets.xor(a, b, 64); }, }, + not: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.not(a, 64); + }, + }, }, }); @@ -57,41 +63,20 @@ await equivalentAsync( } ); -let NOT = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(a) { - Gadgets.not(a, 64); - }, - }, - }, -}); - -await NOT.compile(); - // not [2, 4, 8, 16, 32, 64, 128].forEach((length) => { - equivalent({ from: [uint(length), uint(length)], to: field })( - Fp.not, - (x) => Gadgets.not(x, length) + equivalent({ from: [uint(length), uint(length)], to: field })(Fp.not, (x) => + Gadgets.not(x, length) ); }); - -await equivalentAsync( - { from: [maybeUint64], to: field }, - { runs: 3 } -)( +await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( (x) => { - if (x >= 2n ** 64n) - throw Error('Does not fit into 64 bits'); + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); return Fp.not(x); }, - async (x) => { - let proof = await Bitwise.not(x); + async (a) => { + let proof = await Bitwise.not(a); return proof.publicOutput; } ); - - From f64cd8eebb628421f237d45f3224dde10250b282 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 12:14:12 -0700 Subject: [PATCH 0394/1215] feat(regression_test.json): add missing test case for the "not" operation This commit adds the missing test case with the required "rows" and "digest" properties. --- src/examples/regression_test.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 817796fa3a..fe6b345de4 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -171,6 +171,10 @@ "xor": { "rows": 15, "digest": "b3595a9cc9562d4f4a3a397b6de44971" + }, + "not": { + "rows": 17, + "digest": "e558102138be69839649579eb34f2fe9" } }, "verificationKey": { From a1afdba65275f7ee2e142c301c4955fc07cf40fa Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 13:34:26 -0700 Subject: [PATCH 0395/1215] refactor(gadgets.ts): improve comments and documentation for the `not` function --- src/lib/gadgets/gadgets.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index d68c0c0868..ef92810088 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -62,22 +62,28 @@ const Gadgets = { return xor(a, b, length); }, - /** - * Bitwise NOT gadget on {@link Field} elements, for a specified bit length. Equivalent to the [bitwise NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT), but limited to the given length. - * A NOT gate works by inverting each bit. + * Bitwise NOT gate on {@link Field} elements. Equivalent to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. * - * This gadget builds the NOT operation on a given input using the XOR gate. The NOT operation is applied only up to the specified bit length. + * The `length` parameter lets you define how many bits to NOT. It + * defaults to the size of the field in bits ({@link Fp.sizeInBits}). * - * **Note:** The {@link Field} element input needs to fit into the specified bit length. Otherwise, the `xor` implementation may throw an error. + * **Note:** Specifying a larger `length` parameter adds additional * * * + * constraints. * - * ```typescript - * let a = Field(5); // ... 000101 + * @example + * ```ts + * let a = Field(5); // ... 101 + * let b = not(5,3); // ... 010 * - * let c = not(a, 3); // ... 010 - * c.assertEquals(2); + * b.assertEquals(-6); * ``` - * + * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. */ From aacd892a48b1da20ea95256be77858fb199ddbeb Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 26 Oct 2023 14:22:59 -0700 Subject: [PATCH 0396/1215] feat(bitwise.ts): update calculation of allOnesF bitmask --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index a2b444a307..a35a34a98e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -36,7 +36,7 @@ function not(a: Field, length: number) { } // create a bitmask with all ones - let allOnesF = new Field(BigInt(2 ** length - 1)); + let allOnesF = new Field(2n ** BigInt(length) - 1n); let allOnes = Provable.witness(Field, () => { return allOnesF; From ea6e6df25b1a4b5ca9e559bca86d90b4651a5ce3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 14:36:53 -0700 Subject: [PATCH 0397/1215] chore(bindings): update subproject reference --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index fd87c4ece7..1c99637e94 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit fd87c4ece761d35e05bf679165d49c577f4326ce +Subproject commit 1c99637e94cc6ed0489c5d53afd62c413579230b From b7e427c24a0396556450836a18302814a511ee74 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 14:52:33 -0700 Subject: [PATCH 0398/1215] refactor(bitwise.unit-test.ts): reorganize code --- src/lib/gadgets/bitwise.unit-test.ts | 172 ++++++++++++++++---- src/lib/gadgets/gadgets.unit-test.ts | 231 --------------------------- 2 files changed, 140 insertions(+), 263 deletions(-) delete mode 100644 src/lib/gadgets/gadgets.unit-test.ts diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f395b9ebb6..38ab5904bd 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -12,6 +12,19 @@ import { Gadgets } from './gadgets.js'; import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; +let maybeUint64: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + +let uint = (length: number) => fieldWithRng(Random.biguint(length)); + +// -------------------------- +// Bitwise Gates +// -------------------------- + let Bitwise = ZkProgram({ name: 'bitwise', publicOutput: Field, @@ -28,42 +41,22 @@ let Bitwise = ZkProgram({ return Gadgets.rotate(a, 12, 'left'); }, }, + leftShift: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.leftShift(a, 12); + }, + }, + rightShift: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.rightShift(a, 12); + }, + }, }, }); await Bitwise.compile(); - -let uint = (length: number) => fieldWithRng(Random.biguint(length)); - -[2, 4, 8, 16, 32, 64, 128].forEach((length) => { - equivalent({ from: [uint(length), uint(length)], to: field })( - Fp.xor, - (x, y) => Gadgets.xor(x, y, length) - ); -}); - -test( - Random.uint64, - Random.nat(64), - Random.boolean, - (x, n, direction, assert) => { - let z = Field(x); - let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - } -); - -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), -}; - await equivalentAsync( { from: [maybeUint64, maybeUint64], to: field }, { runs: 3 } @@ -90,6 +83,58 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.leftShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.leftShift(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.rightShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.rightShift(x); + return proof.publicOutput; + } +); + +// -------------------------- +// XOR +// -------------------------- + +[2, 4, 8, 16, 32, 64, 128].forEach((length) => { + equivalent({ from: [uint(length), uint(length)], to: field })( + Fp.xor, + (x, y) => Gadgets.xor(x, y, length) + ); +}); + +// -------------------------- +// ROT +// -------------------------- + +test( + Random.uint64, + Random.nat(64), + Random.boolean, + (x, n, direction, assert) => { + let z = Field(x); + let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); + Provable.asProver(() => assert(r1 === r2.toBigInt())); + }); + } +); + function testRot( field: Field, bits: number, @@ -112,3 +157,66 @@ testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); + +// -------------------------- +// Shift +// -------------------------- + +function testShift( + field: Field, + bits: number, + mode: 'left' | 'right', + result: Field +) { + Provable.runAndCheck(() => { + let output = + mode === 'left' + ? Gadgets.leftShift(field, bits) + : Gadgets.rightShift(field, bits); + output.assertEquals( + result, + `${mode === 'left' ? 'ls' : 'rs'}(${field}, ${bits})` + ); + }); +} + +testShift(Field(0), 1, 'left', Field(0)); +testShift(Field(0), 1, 'right', Field(0)); +testShift(Field(1), 1, 'left', Field(2)); +testShift(Field(1), 1, 'right', Field(0)); +testShift(Field(256), 4, 'right', Field(16)); +testShift(Field(256), 20, 'right', Field(0)); +testShift(Field(6510615555426900570n), 16, 'right', Field(99344109427290n)); +testShift( + Field(18446744073709551615n), + 15, + 'left', + Field(18446744073709518848n) +); +testShift(Field(12523523412423524646n), 32, 'right', Field(2915860016)); +testShift( + Field(12523523412423524646n), + 32, + 'left', + Field(17134720101237391360n) +); + +test(Random.uint64, Random.nat(64), (x, n, assert) => { + let z = Field(x); + let r = Fp.leftShift(x, n); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let o = Gadgets.leftShift(f, n); + Provable.asProver(() => assert(r === o.toBigInt())); + }); +}); + +test(Random.uint64, Random.nat(64), (x, n, assert) => { + let z = Field(x); + let r = Fp.rightShift(x, n); + Provable.runAndCheck(() => { + let f = Provable.witness(Field, () => z); + let o = Gadgets.rightShift(f, n); + Provable.asProver(() => assert(r === o.toBigInt())); + }); +}); diff --git a/src/lib/gadgets/gadgets.unit-test.ts b/src/lib/gadgets/gadgets.unit-test.ts deleted file mode 100644 index 1eeaf4670e..0000000000 --- a/src/lib/gadgets/gadgets.unit-test.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { mod } from '../../bindings/crypto/finite_field.js'; -import { Field } from '../../lib/core.js'; -import { ZkProgram } from '../proof_system.js'; -import { - Spec, - boolean, - equivalentAsync, - field, -} from '../testing/equivalent.js'; -import { test, Random } from '../testing/property.js'; -import { Field as Fp } from '../../provable/field-bigint.js'; -import { Gadgets } from './gadgets.js'; -import { Provable } from '../provable.js'; -import { Bool } from '../core.js'; - -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), -}; - -// TODO: make a ZkFunction or something that doesn't go through Pickles -// -------------------------- -// RangeCheck64 Gate -// -------------------------- - -let RangeCheck64 = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.rangeCheck64(x); - }, - }, - }, -}); - -await RangeCheck64.compile(); - -// TODO: we use this as a test because there's no way to check custom gates quickly :( -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( - (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); - return true; - }, - async (x) => { - let proof = await RangeCheck64.run(x); - return await RangeCheck64.verify(proof); - } -); - -// -------------------------- -// ROT Gate -// -------------------------- -let ROT = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.rot(x, 2, 'left'); - Gadgets.rot(x, 2, 'right'); - }, - }, - }, -}); - -await ROT.compile(); -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( - (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); - return true; - }, - async (x) => { - let proof = await ROT.run(x); - return await ROT.verify(proof); - } -); - -function testRot( - field: Field, - bits: number, - mode: 'left' | 'right', - result: Field -) { - Provable.runAndCheck(() => { - let w = Provable.witness(Field, () => field); - let r = Provable.witness(Field, () => result); - let output = Gadgets.rot(w, bits, mode); - output.assertEquals(r, `rot(${field}, ${bits}, ${mode})`); - }); -} - -testRot(Field(0), 0, 'left', Field(0)); -testRot(Field(0), 32, 'right', Field(0)); -testRot(Field(1), 1, 'left', Field(2)); -testRot(Field(1), 63, 'left', Field('9223372036854775808')); -testRot(Field(256), 4, 'right', Field(16)); -testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); -testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); -testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); - -// rotation -test( - Random.uint64, - Random.nat(64), - Random.boolean, - (x, n, direction, assert) => { - let z = Field(x); - let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rot(f, n, direction ? 'left' : 'right'); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - } -); - -// -------------------------- -// Shift Gates -// -------------------------- - -function testShift( - field: Field, - bits: number, - mode: 'left' | 'right', - result: Field -) { - Provable.runAndCheck(() => { - let w = Provable.witness(Field, () => field); - let r = Provable.witness(Field, () => result); - let output = Provable.if( - Bool(mode === 'left'), - Gadgets.leftShift(w, bits), - Gadgets.rightShift(w, bits) - ); - output.assertEquals( - r, - `${mode === 'left' ? 'ls' : 'rs'}(${field}, ${bits})` - ); - }); -} - -let LS = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.leftShift(x, 2); - }, - }, - }, -}); - -await LS.compile(); -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( - (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); - return true; - }, - async (x) => { - let proof = await LS.run(x); - return await LS.verify(proof); - } -); - -let RS = ZkProgram({ - methods: { - run: { - privateInputs: [Field], - method(x) { - Gadgets.rightShift(x, 2); - }, - }, - }, -}); - -await RS.compile(); -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( - (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); - return true; - }, - async (x) => { - let proof = await RS.run(x); - return await RS.verify(proof); - } -); - -testShift(Field(0), 1, 'left', Field(0)); -testShift(Field(0), 1, 'right', Field(0)); -testShift(Field(1), 1, 'left', Field(2)); -testShift(Field(1), 1, 'right', Field(0)); -testShift(Field(256), 4, 'right', Field(16)); -testShift(Field(256), 20, 'right', Field(0)); -testShift(Field(6510615555426900570n), 16, 'right', Field(99344109427290n)); -testShift( - Field(18446744073709551615n), - 15, - 'left', - Field(18446744073709518848n) -); -testShift(Field(12523523412423524646n), 32, 'right', Field(2915860016)); -testShift( - Field(12523523412423524646n), - 32, - 'left', - Field(17134720101237391360n) -); - -test(Random.uint64, Random.nat(64), (x, n, assert) => { - let z = Field(x); - let r = Fp.leftShift(x, n); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let o = Gadgets.leftShift(f, n); - Provable.asProver(() => assert(r === o.toBigInt())); - }); -}); - -test(Random.uint64, Random.nat(64), (x, n, assert) => { - let z = Field(x); - let r = Fp.rightShift(x, n); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let o = Gadgets.rightShift(f, n); - Provable.asProver(() => assert(r === o.toBigInt())); - }); -}); From ffe1af6ec847b82d1607d2a811ea438f9a0eea18 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 14:58:51 -0700 Subject: [PATCH 0399/1215] chore(bindings): update subproject reference --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1c99637e94..169c97fa2a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1c99637e94cc6ed0489c5d53afd62c413579230b +Subproject commit 169c97fa2ab1ef1ee345496795a067756fd8eab2 From e74b623cbb9a49699e78973d3511774d1d598eec Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 15:04:49 -0700 Subject: [PATCH 0400/1215] docs(gadgets.ts): update comments for better clarity --- src/lib/gadgets/gadgets.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 1ef2d0cdc0..5e407b7625 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -55,11 +55,11 @@ const Gadgets = { * * @example * ```ts - * const x = Provable.witness(Field, () => Field(0b001100)); + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary * const y = rot(x, 2, 'left'); // left rotation by 2 bits * const z = rot(x, 2, 'right'); // right rotation by 2 bits - * y.assertEquals(0b110000); - * z.assertEquals(0b000011) + * y.assertEquals(0b110000); // 48 in binary + * z.assertEquals(0b000011) // 3 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits @@ -110,19 +110,22 @@ const Gadgets = { * This is akin to the `<<` shift operation in JavaScript, where bits are moved to the left. * The `leftShift` function uses the rotation method internally to achieve this operation. * - * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. - * For elements that exceed 64 bits, this operation will fail. + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * Therefore, to safely use `leftShift()`, you need to make sure that the values passed in are range checked to 64 bits. + * For example, this can be done with {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to shift. - * @param bits Amount of bits to shift the {@link Field} element to the left. + * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 64 (or else the shift will fail). * * @throws Throws an error if the input value exceeds 64 bits. * * @example * ```ts - * const x = Provable.witness(Field, () => Field(12)); + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary * const y = leftShift(x, 2); // left shift by 2 bits - * y.assertEquals(48); + * y.assertEquals(0b110000); // 48 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits @@ -137,19 +140,22 @@ const Gadgets = { * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. * The `rightShift` function utilizes the rotation method internally to implement this operation. * - * **Note:** You cannot shift {@link Field} elements that exceed 64 bits. - * For elements that exceed 64 bits, this operation will fail. + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * Therefore, to safely use `rightShift()`, you need to make sure that the values passed in are range checked to 64 bits. + * For example, this can be done with {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to shift. - * @param bits Amount of bits to shift the {@link Field} element to the right. + * @param bits Amount of bits to shift the {@link Field} element to the right. The amount should be between 0 and 64 (or else the shift will fail). * * @throws Throws an error if the input value exceeds 64 bits. * * @example * ```ts - * const x = Provable.witness(Field, () => Field(48)); + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary * const y = rightShift(x, 2); // right shift by 2 bits - * y.assertEquals(12); + * y.assertEquals(0b000011); // 3 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits From 575920dbd2a4e50564ca422de7e7da4cc9b57542 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 15:11:53 -0700 Subject: [PATCH 0401/1215] docs(gadgets.ts): update leftShift function documentation --- src/lib/gadgets/gadgets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 5e407b7625..a6e7da13d1 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -107,8 +107,8 @@ const Gadgets = { /** * Performs a left shift operation on the provided {@link Field} element. - * This is akin to the `<<` shift operation in JavaScript, where bits are moved to the left. - * The `leftShift` function uses the rotation method internally to achieve this operation. + * This operation is akin to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. * * **Important:** The gadgets assumes that its input is at most 64 bits in size. * From 93a4d5047a366641c307ed090da734af92c40f48 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 15:43:55 -0700 Subject: [PATCH 0402/1215] chore(bindings): update bindings submodule --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 26723fd698..2ca738394e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 26723fd6987db505a8c44cc41e01b17ac496c22f +Subproject commit 2ca738394ee68d1b6bf07b4317c634066b07d748 From 54e1e621a262cdca0069e293f9b52cc471c5c56b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 26 Oct 2023 15:46:58 -0700 Subject: [PATCH 0403/1215] chore(mina): update mina submodule to o1js-main --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 396ae46c0f..28aef4fefa 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 396ae46c0f301f697c91771bc6780571a7656a45 +Subproject commit 28aef4fefa0e23e107471b96053e6364c23f6d4e From ba501f81fa1b4e3491f4c3b8c12714f55eee1bc7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:29:42 +0200 Subject: [PATCH 0404/1215] remove excess range check in rot gadget --- src/lib/gadgets/bitwise.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index b51f90a801..73ccbd4448 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -194,7 +194,9 @@ function rot( ); // Compute next row Gates.rangeCheck64(shifted); - // Compute following row - Gates.rangeCheck64(excess); + // note: range-checking `shifted` and `field` is enough. + // * excess < 2^rot follows from the bound check and the rotation equation in the gate + // * rotated < 2^64 follows from rotated = excess + shifted (because shifted has to be a multiple of 2^rot) + // for a proof, see TODO return [rotated, excess, shifted]; } From c1474ee79872b1136f081ddd218eefbcf59443f8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:30:15 +0200 Subject: [PATCH 0405/1215] update cs test and json --- src/examples/primitive_constraint_system.ts | 1 + src/examples/regression_test.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 4f1f3f95ad..8f81efbee9 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -66,6 +66,7 @@ const GroupMock = { const BitwiseMock = { rot() { let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rangeCheck64(a); // `rotate()` doesn't do this Gadgets.rotate(a, 2, 'left'); Gadgets.rotate(a, 2, 'right'); Gadgets.rotate(a, 4, 'left'); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 40e164b481..8fa3abbc50 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -169,8 +169,8 @@ "digest": "Bitwise Primitive", "methods": { "rot": { - "rows": 13, - "digest": "2c0dadbba96fd7ddb9adb7d643425ce3" + "rows": 10, + "digest": "c38703de755b10edf77bf24269089274" }, "xor": { "rows": 15, From e11cc19f50b445956736131a32362454672ddeaa Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:30:44 +0200 Subject: [PATCH 0406/1215] fixup bitwise unit test to test equivalence in provable code --- src/lib/gadgets/bitwise.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f395b9ebb6..1d5c457002 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -1,7 +1,7 @@ import { ZkProgram } from '../proof_system.js'; import { Spec, - equivalent, + equivalentProvable as equivalent, equivalentAsync, field, fieldWithRng, From 9a58bc725cf4070ebe2a55d351860070ae1f9ea8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:31:36 +0200 Subject: [PATCH 0407/1215] fill in PR link --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 73ccbd4448..b5edb5df5a 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -197,6 +197,6 @@ function rot( // note: range-checking `shifted` and `field` is enough. // * excess < 2^rot follows from the bound check and the rotation equation in the gate // * rotated < 2^64 follows from rotated = excess + shifted (because shifted has to be a multiple of 2^rot) - // for a proof, see TODO + // for a proof, see https://github.com/o1-labs/o1js/pull/1201 return [rotated, excess, shifted]; } From 1270b4ee11e5dfffac221746c970c823cc4b7216 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:48:05 +0200 Subject: [PATCH 0408/1215] clarify reference bit length of 64 in rotation gadget --- src/lib/gadgets/gadgets.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 59e4a8e305..2035f688ad 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -36,16 +36,20 @@ const Gadgets = { }, /** - * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, with the distinction that the bits are circulated to the opposite end rather than being discarded. - * For a left rotation, this means that bits shifted off the left end reappear at the right end. Conversely, for a right rotation, bits shifted off the right end reappear at the left end. - * It’s important to note that these operations are performed considering the binary representation of the number in big-endian format, where the most significant bit is on the left end and the least significant bit is on the right end. + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. * - * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * **Important:** The gadget assumes that its input is at most 64 bits in size. * * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. - * Therefore, to safely use `rotate()`, you need to make sure that the values passed in are range checked to 64 bits. - * For example, this can be done with {@link Gadgets.rangeCheck64}. + * To safely use `rotate()`, you need to make sure that the value passed in is range checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to rotate. * @param bits amount of bits to rotate this {@link Field} element with. From 3fad72c2f2ebac2993205497d5d07b677f8fed44 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 15:50:13 +0200 Subject: [PATCH 0409/1215] fix doccomment re errors when input is > 64 bits --- src/lib/gadgets/gadgets.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 2035f688ad..62102cf605 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -48,25 +48,20 @@ const Gadgets = { * **Important:** The gadget assumes that its input is at most 64 bits in size. * * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. - * To safely use `rotate()`, you need to make sure that the value passed in is range checked to 64 bits; + * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to rotate. * @param bits amount of bits to rotate this {@link Field} element with. * @param direction left or right rotation direction. * - * @throws Throws an error if the input value exceeds 64 bits. - * * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); - * const y = rot(x, 2, 'left'); // left rotation by 2 bits - * const z = rot(x, 2, 'right'); // right rotation by 2 bits + * const y = Gadgets.rotate(x, 2, 'left'); // left rotation by 2 bits + * const z = Gadgets.rotate(x, 2, 'right'); // right rotation by 2 bits * y.assertEquals(0b110000); * z.assertEquals(0b000011) - * - * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * rot(xLarge, 32, "left"); // throws an error since input exceeds 64 bits * ``` */ rotate(field: Field, bits: number, direction: 'left' | 'right' = 'left') { From 5c7747beacbe1d7b7133fce01582d6c272443124 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 16:02:49 +0200 Subject: [PATCH 0410/1215] more doccomment fixes --- src/lib/gadgets/gadgets.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 62102cf605..5bcf66bd7c 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -21,10 +21,10 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(12345678n)); - * rangeCheck64(x); // successfully proves 64-bit range + * Gadgets.rangeCheck64(x); // successfully proves 64-bit range * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * rangeCheck64(xLarge); // throws an error since input exceeds 64 bits + * Gadgets.rangeCheck64(xLarge); // throws an error since input exceeds 64 bits * ``` * * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, @@ -55,6 +55,8 @@ const Gadgets = { * @param bits amount of bits to rotate this {@link Field} element with. * @param direction left or right rotation direction. * + * @throws Throws an error if the input value exceeds 64 bits. + * * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); @@ -96,7 +98,7 @@ const Gadgets = { * let a = Field(5); // ... 000101 * let b = Field(3); // ... 000011 * - * let c = xor(a, b, 2); // ... 000110 + * let c = Gadgets.xor(a, b, 2); // ... 000110 * c.assertEquals(6); * ``` */ From 9893b903d0a9e694b6a46df4bdf1bc24b7bc4561 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 27 Oct 2023 16:05:18 +0200 Subject: [PATCH 0411/1215] add back error example since its the behaviour in practice when creating a proof non-maliciously --- src/lib/gadgets/gadgets.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 5bcf66bd7c..f7b5126a23 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -63,7 +63,10 @@ const Gadgets = { * const y = Gadgets.rotate(x, 2, 'left'); // left rotation by 2 bits * const z = Gadgets.rotate(x, 2, 'right'); // right rotation by 2 bits * y.assertEquals(0b110000); - * z.assertEquals(0b000011) + * z.assertEquals(0b000011); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rotate(xLarge, 32, "left"); // throws an error since input exceeds 64 bits * ``` */ rotate(field: Field, bits: number, direction: 'left' | 'right' = 'left') { From 75359152c950ef206b4756464da6eda526899d9b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 27 Oct 2023 12:35:59 -0700 Subject: [PATCH 0412/1215] chore(bindings): update subproject commit hash to 3f2ccd8 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 2ca738394e..3f2ccd8757 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 2ca738394ee68d1b6bf07b4317c634066b07d748 +Subproject commit 3f2ccd875744a2be0fe8e99e84a33b16a3ee3198 From 83468a284ad38703d2a123540ee07d6227ffb054 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 27 Oct 2023 12:36:26 -0700 Subject: [PATCH 0413/1215] fix(package.json): update 'make' script to use local script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa0277d7c2..8bbe54f376 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "scripts": { "type-check": "tsc --noEmit", "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", - "make": "make -C ../../.. snarkyjs", + "make": "./src/bindings/scripts/update-snarkyjs-bindings.sh", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", From dcf8831a115650123bf9843fb905963314827037 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 27 Oct 2023 12:36:57 -0700 Subject: [PATCH 0414/1215] fix(tsconfig.json): add './src/mina' to exclude list to prevent unnecessary compilation of mina directory --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 569ef2b1e6..7eb2e0ba4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "include": ["./src/**/*.ts"], - "exclude": ["./src/**/*.bc.js", "./src/build", "./src/examples"], + "exclude": ["./src/**/*.bc.js", "./src/build", "./src/examples", "./src/mina"], "compilerOptions": { "rootDir": "./src", "outDir": "dist", From 779417621b9fc3ad348c92d4cc615ce71161aae6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 27 Oct 2023 16:13:16 -0700 Subject: [PATCH 0415/1215] fix(.gitmodules): change MinaProtocol submodule URL from SSH to HTTPS --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index b8be721ce9..76a36100bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/o1-labs/snarkyjs-bindings.git [submodule "src/mina"] path = src/mina - url = git@github.com:MinaProtocol/mina.git + url = https://github.com/MinaProtocol/mina.git From d76da766d78291d3eed03316ad51abaabd06e79a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 27 Oct 2023 16:47:05 -0700 Subject: [PATCH 0416/1215] feat(jest.config.js): add 'src/mina/' to modulePathIgnorePatterns to prevent Jest from running tests in this directory --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index b24c895d96..7f6d944074 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,7 @@ export default { testEnvironment: 'node', extensionsToTreatAsEsm: ['.ts'], transformIgnorePatterns: ['node_modules/', 'dist/node/'], + modulePathIgnorePatterns: ['src/mina/'], globals: { 'ts-jest': { useESM: true, From 7b946eef7d6c4f42dad665354e3ffd60c6cb297d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 28 Oct 2023 15:00:14 -0700 Subject: [PATCH 0417/1215] fix(package.json): update build:docs script to use tsconfig.web.json for typedoc generation to ensure correct TypeScript configuration is used for documentation generation --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ade0ac845..6f5e803187 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", - "build:docs": "npx typedoc", + "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", "serve:web": "cp src/bindings/compiled/web_bindings/server.js src/bindings/compiled/web_bindings/index.html src/examples/simple_zkapp.js dist/web && node dist/web/server.js", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", From 5069c051deaa6737eed24d7a2b8d040cd99a6def Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 29 Oct 2023 10:42:24 -0700 Subject: [PATCH 0418/1215] feat(run-jest-tests.sh): add condition to exclude 'src/mina' from jest tests This change is temporary until 'snarkyjs' is removed from the mina repo. --- run-jest-tests.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/run-jest-tests.sh b/run-jest-tests.sh index d103274798..f034d80158 100755 --- a/run-jest-tests.sh +++ b/run-jest-tests.sh @@ -3,5 +3,8 @@ set -e shopt -s globstar # to expand '**' into nested directories for f in ./src/**/*.test.ts; do - NODE_OPTIONS=--experimental-vm-modules npx jest $f; -done + # TODO: Remove this once we remove the `snarkyjs` inside the mina repo + if [[ $f != *"src/mina"* ]]; then + NODE_OPTIONS=--experimental-vm-modules npx jest $f; + fi +done \ No newline at end of file From e3c20452afa75f082eef1fe8dfc4dc42b13deb3e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 29 Oct 2023 20:22:32 -0700 Subject: [PATCH 0419/1215] feat(README-dev): first draft --- README-dev.md | 125 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/README-dev.md b/README-dev.md index 2cd50c025c..c92ff4fc5c 100644 --- a/README-dev.md +++ b/README-dev.md @@ -1,74 +1,111 @@ -# How to contribute to the o1js codebase +# o1js README-dev -This README includes information that is helpful for o1js core contributors. +o1js is a TypeScript framework designed for zk-SNARKs and zkApps on the Mina blockchain. -## Run examples using Node.js +- [zkApps Overview](https://docs.minaprotocol.com/zkapps) +- [Mina README](/src/mina/README.md) + +For more information on our development process and how to contribute, see [CONTRIBUTING.md](https://github.com/o1-labs/o1js/blob/main/CONTRIBUTING.md). This document is meant to guide you through building o1js from source and understanding the development workflow. + +## Prerequisites + +Before starting, ensure you have the following tools installed: + +- [Git](https://git-scm.com/) +- [Node.js and npm](https://nodejs.org/) +- [opam](https://opam.ocaml.org/) +- [Cargo](https://www.rust-lang.org/learn/get-started) + +After cloning the repository, you need to fetch the submodules: + +```sh +git submodule update --init --recursive +``` + +## Building o1js + +For most users, building o1js is as simple as running: ```sh npm install npm run build - -./run src/examples/api_exploration.ts ``` -## Build and run the web version +This will compile the TypeScript source files, making it ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end-users. These artifacts are stored under `src/bindings/compiled`, and contain the artifacts needed for both node and web builds. These files do not have to be regenerated unless there are changes to the OCaml or Rust source files. + +## Building Bindings + +If you need to regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js, so you can build them from within the o1js repo. + +o1js depends on OCaml code that is transplied to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [snarky](https://github.com/o1-labs/snarky) and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. + +The compiled artifacts are stored under `src/bindings/compiled`, and are version-controlled to simplify the build process for end-users. + +If you wish to rebuild the OCaml and Rust artifacts, you must be able to build the Mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. Once you have configured your environment to build Mina, you can build the bindings: ```sh -npm install -npm run build:web -npm run serve:web +npm run make ``` -To see the test running in a web browser, go to `http://localhost:8000/`. +This will build the OCaml and Rust artifacts, and copy them to the `src/bindings/compiled` directory. + +### OCaml Bindings + +The OCaml bindings are located under `src/bindings`, and they specify all of the low-level OCaml code that is exposed to o1js. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. -## Run tests +### WebAssembly Bindings -- Unit tests +The WebAssembly bindings are built using Rust's `wasm-pack`. Ensure you have it installed and configured correctly. - ```sh - npm run test - npm run test:unit - ``` +## Development -- Integration tests +### Branch Compatibility - ```sh - npm run test:integration - ``` +When working with submodules and various interconnected parts of the stack, ensure you are on the correct branches that are compatible with each other. -- E2E tests +#### How to Use the Branches - ```sh - npm install - npm run e2e:install - npm run build:web +| Repository | mina -> o1js -> o1js-bindings | +| ---------- | -------------------------------- | +| Branches | o1js-main -> main -> main | +| | berkeley -> berkeley -> berkeley | +| | develop -> develop -> develop | - npm run e2e:prepare-server - npm run test:e2e - npm run e2e:show-report - ``` +- `o1js-main`: The o1js-main branch in the Mina repository corresponds to the main branch in both o1js and o1js-bindings repositories. This is where stable releases and ramp-up features are maintained. -## Branch Compatibility +- `berkeley`: The berkeley branch is maintained across all three repositories. This branch is used for features and updates specific to the Berkeley release of the project. -o1js is mostly used to write Mina Smart Contracts and must be compatible with the latest Berkeley Testnet, or soon Mainnet. +- `develop`: The develop branch is also maintained across all three repositories. It is used for ongoing development, testing new features, and integration work. -The OCaml code is in the o1js-bindings repository, not directly in o1js. +### Running Tests -To maintain compatibility between the repositories and build o1js from the [Mina repository](https://github.com/MinaProtocol/mina), make changes to its core, such as the OCaml-bindings in the [o1js-bindings repository](https://github.com/o1-labs/o1js-bindings), you must follow a certain branch compatibility pattern: +To ensure your changes don't break existing functionality, run the test suite: -The following branches are compatible: +```sh +npm run test +npm run test:unit +``` -| repository | mina -> o1js -> o1js-bindings | -| ---------- | ------------------------------------- | -| branches | rampup -> main -> main | -| | berkeley -> berkeley -> berkeley | -| | develop -> develop -> develop | +This will run all the unit tests and provide you with a summary of the test results. -## Run the GitHub actions locally +You can additionally run integration tests by running: - -You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: +```sh +npm run test:integration ``` -act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN + +Finally, we have a set of end-to-end tests that run against the browser. These tests are not run by default, but you can run them by running: + +```sh +npm install +npm run e2e:install +npm run build:web + +npm run e2e:prepare-server +npm run test:e2e +npm run e2e:show-report ``` -to execute the job "Build-And-Test-Server for the test type `Simple integration tests`. + +### Releasing + +To release a new version of o1js, you must first update the version number in `package.json`. Then, you can create a new pull request to merge your changes into the main branch. Once the pull request is merged, a CI job will automatically publish the new version to npm. From b219f89614567c9678073e1736b83181a934716e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 10:02:27 +0100 Subject: [PATCH 0420/1215] tweak xor docs --- src/lib/gadgets/gadgets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f7b5126a23..3426e9d61f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -98,11 +98,11 @@ const Gadgets = { * * @example * ```ts - * let a = Field(5); // ... 000101 - * let b = Field(3); // ... 000011 + * let a = Field(0b0101); + * let b = Field(0b0011); * - * let c = Gadgets.xor(a, b, 2); // ... 000110 - * c.assertEquals(6); + * let c = Gadgets.xor(a, b, 4); // xor-ing 4 bits + * c.assertEquals(0b0110); * ``` */ xor(a: Field, b: Field, length: number) { From bb14474be47cc1ac619ec7b1893ad24b85b071fd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 12:48:25 +0100 Subject: [PATCH 0421/1215] introduce header type and implement parsing in storable --- src/lib/ml/base.ts | 2 + src/lib/proof-system/prover-keys.ts | 92 +++++++++++++++++++++++++++-- src/lib/proof_system.ts | 5 ++ src/lib/storable.ts | 25 +++++++- src/snarky.d.ts | 6 ++ 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 10243efab7..130e6b5357 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -10,6 +10,7 @@ export { MlBytes, MlResult, MlUnit, + MlString, }; // ocaml types @@ -27,6 +28,7 @@ type MlUnit = 0; * see https://github.com/ocsigen/js_of_ocaml/blob/master/runtime/mlBytes.js */ type MlBytes = { t: number; c: string; l: number }; +type MlString = MlBytes; const MlArray = { to(arr: T[]): MlArray { diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index dbaf832042..ede80f406d 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -5,8 +5,18 @@ import { import { Pickles, getWasm } from '../../snarky.js'; import { VerifierIndex } from '../../bindings/crypto/bindings/kimchi-types.js'; import { getRustConversion } from '../../bindings/crypto/bindings.js'; - -export { encodeProverKey, decodeProverKey, proverKeyType, AnyKey, AnyValue }; +import { MlString } from '../ml/base.js'; +import { CacheHeader, cacheHeaderVersion } from '../storable.js'; +import type { MethodInterface } from '../proof_system.js'; + +export { + parseHeader, + encodeProverKey, + decodeProverKey, + proverKeyType, + AnyKey, + AnyValue, +}; export type { MlWrapVerificationKey }; // Plonk_constraint_system.Make()().t @@ -23,23 +33,52 @@ type MlBackendKeyPair = [ cs: MlConstraintSystem ]; +// Snarky_keys_header.t + +type MlSnarkKeysHeader = [ + _: 0, + headerVersion: number, + kind: [_: 0, type: MlString, identifier: MlString], + constraintConstants: unknown, + commits: unknown, + length: number, + commitDate: MlString, + constraintSystemHash: MlString, + identifyingHash: MlString +]; + // Pickles.Cache.{Step,Wrap}.Key.Proving.t type MlStepProvingKeyHeader = [ _: 0, typeEqual: number, - snarkKeysHeader: unknown, + snarkKeysHeader: MlSnarkKeysHeader, index: number, constraintSystem: MlConstraintSystem ]; +type MlStepVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + index: number, + digest: unknown +]; + type MlWrapProvingKeyHeader = [ _: 0, typeEqual: number, - snarkKeysHeader: unknown, + snarkKeysHeader: MlSnarkKeysHeader, constraintSystem: MlConstraintSystem ]; +type MlWrapVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + digest: unknown +]; + // Pickles.Verification_key.t class MlWrapVerificationKey { @@ -59,9 +98,9 @@ enum KeyType { type AnyKey = | [KeyType.StepProvingKey, MlStepProvingKeyHeader] - | [KeyType.StepVerificationKey, unknown] + | [KeyType.StepVerificationKey, MlStepVerificationKeyHeader] | [KeyType.WrapProvingKey, MlWrapProvingKeyHeader] - | [KeyType.WrapVerificationKey, unknown]; + | [KeyType.WrapVerificationKey, MlWrapVerificationKeyHeader]; type AnyValue = | [KeyType.StepProvingKey, MlBackendKeyPair] @@ -69,6 +108,40 @@ type AnyValue = | [KeyType.WrapProvingKey, MlBackendKeyPair] | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; +function parseHeader( + programId: string, + methods: MethodInterface[], + key: AnyKey +): CacheHeader { + let hash = Pickles.util.fromMlString(key[1][2][8]); + switch (key[0]) { + case KeyType.StepProvingKey: + case KeyType.StepVerificationKey: { + let kind = snarkKeyStringKind[key[0]]; + let methodIndex = key[1][3]; + let methodName = methods[methodIndex].methodName; + // TODO sanitize unique id + let uniqueId = `${kind}-${programId}-${methodIndex}-${methodName}-${hash}`; + return { + version: cacheHeaderVersion, + uniqueId, + kind, + programId, + methodName, + methodIndex, + hash, + }; + } + case KeyType.WrapProvingKey: + case KeyType.WrapVerificationKey: { + let kind = snarkKeyStringKind[key[0]]; + // TODO sanitize unique id + let uniqueId = `${kind}-${programId}-${hash}`; + return { version: cacheHeaderVersion, uniqueId, kind, programId, hash }; + } + } +} + function encodeProverKey(value: AnyValue): Uint8Array { let wasm = getWasm(); switch (value[0]) { @@ -137,6 +210,13 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { } } +const snarkKeyStringKind = { + [KeyType.StepProvingKey]: 'step-pk', + [KeyType.StepVerificationKey]: 'step-vk', + [KeyType.WrapProvingKey]: 'wrap-pk', + [KeyType.WrapVerificationKey]: 'wrap-vk', +} as const; + const proverKeySerializationType = { [KeyType.StepProvingKey]: 'bytes', [KeyType.StepVerificationKey]: 'string', diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 50f499053f..fb07aafda1 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -32,6 +32,7 @@ import { Storable } from './storable.js'; import { decodeProverKey, encodeProverKey, + parseHeader, proverKeyType, } from './proof-system/prover-keys.js'; @@ -590,6 +591,8 @@ async function compileProgram({ let storable: Pickles.Storable = [ 0, function read_(key, path) { + // TODO sanitize program name + let header = parseHeader(proofSystemTag.name, methodIntfs, key); try { let type = proverKeyType(key); let bytes = read(path, type); @@ -599,6 +602,8 @@ async function compileProgram({ } }, function write_(key, value, path) { + // TODO sanitize program name + let header = parseHeader(proofSystemTag.name, methodIntfs, key); try { let type = proverKeyType(key); let bytes = encodeProverKey(value); diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 42046bae58..1f9b9b93be 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -7,7 +7,7 @@ import { } from './util/fs.js'; import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; -export { Storable }; +export { Storable, CacheHeader, cacheHeaderVersion }; /** * Interface for storing and retrieving values, for caching. @@ -32,6 +32,29 @@ type Storable = { write(key: string, value: Uint8Array, type: 'string' | 'bytes'): void; }; +const cacheHeaderVersion = 0; + +type CommonHeader = { version: number; uniqueId: string }; +type StepKeyHeader = { + kind: Kind; + programId: string; + methodName: string; + methodIndex: number; + hash: string; +}; +type WrapKeyHeader = { kind: Kind; programId: string; hash: string }; + +/** + * A header that is passed to the caching layer, to support richer caching strategies. + */ +type CacheHeader = ( + | StepKeyHeader<'step-pk'> + | StepKeyHeader<'step-vk'> + | WrapKeyHeader<'wrap-pk'> + | WrapKeyHeader<'wrap-vk'> +) & + CommonHeader; + const None: Storable = { read() { throw Error('not available'); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 65d842fe94..e7d7f4a1ad 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -11,6 +11,7 @@ import type { MlBytes, MlResult, MlUnit, + MlString, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; import type * as ProverKeys from './lib/proof-system/prover-keys.js'; @@ -728,4 +729,9 @@ declare const Pickles: { ) => [N, Pickles.Proof]; proofToBase64Transaction: (proof: Pickles.Proof) => string; + + util: { + toMlString(s: string): MlString; + fromMlString(s: MlString): string; + }; }; From eb6251b35b15b5c5cb692a8cd4bf296913dc8d19 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 13:16:38 +0100 Subject: [PATCH 0422/1215] rename storable to cache and pass header as input --- src/index.ts | 2 +- src/lib/proof-system/prover-keys.ts | 26 ++++++------- src/lib/proof_system.ts | 25 ++++++------ src/lib/storable.ts | 59 ++++++++++++++++++----------- src/lib/zkapp.ts | 6 +-- src/snarky.d.ts | 4 +- 6 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/index.ts b/src/index.ts index 386db42f78..257fd64de0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,7 +45,7 @@ export { Undefined, Void, } from './lib/proof_system.js'; -export { Storable } from './lib/storable.js'; +export { Cache } from './lib/storable.js'; export { Token, diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index ede80f406d..6161d6e6dd 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -9,14 +9,7 @@ import { MlString } from '../ml/base.js'; import { CacheHeader, cacheHeaderVersion } from '../storable.js'; import type { MethodInterface } from '../proof_system.js'; -export { - parseHeader, - encodeProverKey, - decodeProverKey, - proverKeyType, - AnyKey, - AnyValue, -}; +export { parseHeader, encodeProverKey, decodeProverKey, AnyKey, AnyValue }; export type { MlWrapVerificationKey }; // Plonk_constraint_system.Make()().t @@ -130,14 +123,23 @@ function parseHeader( methodName, methodIndex, hash, + dataType: snarkKeySerializationType[key[0]], }; } case KeyType.WrapProvingKey: case KeyType.WrapVerificationKey: { let kind = snarkKeyStringKind[key[0]]; + let dataType = snarkKeySerializationType[key[0]]; // TODO sanitize unique id let uniqueId = `${kind}-${programId}-${hash}`; - return { version: cacheHeaderVersion, uniqueId, kind, programId, hash }; + return { + version: cacheHeaderVersion, + uniqueId, + kind, + programId, + hash, + dataType, + }; } } } @@ -217,13 +219,9 @@ const snarkKeyStringKind = { [KeyType.WrapVerificationKey]: 'wrap-vk', } as const; -const proverKeySerializationType = { +const snarkKeySerializationType = { [KeyType.StepProvingKey]: 'bytes', [KeyType.StepVerificationKey]: 'string', [KeyType.WrapProvingKey]: 'bytes', [KeyType.WrapVerificationKey]: 'string', } as const; - -function proverKeyType(key: AnyKey): 'string' | 'bytes' { - return proverKeySerializationType[key[0]]; -} diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index fb07aafda1..49388e6b1c 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -28,12 +28,11 @@ import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlTuple, MlUnit } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; -import { Storable } from './storable.js'; +import { Cache } from './storable.js'; import { decodeProverKey, encodeProverKey, parseHeader, - proverKeyType, } from './proof-system/prover-keys.js'; // public API @@ -317,7 +316,7 @@ function ZkProgram< } | undefined; - async function compile({ storable = Storable.FileSystemDefault } = {}) { + async function compile({ cache = Cache.FileSystemDefault } = {}) { let methodsMeta = analyzeMethods(); let gates = methodsMeta.map((m) => m.gates); let { provers, verify, verificationKey } = await compileProgram({ @@ -327,7 +326,7 @@ function ZkProgram< methods: methodFunctions, gates, proofSystemTag: selfTag, - storable, + cache, overrideWrapDomain: config.overrideWrapDomain, }); compileOutput = { provers, verify }; @@ -563,7 +562,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - storable: { read, write }, + cache: { read, write }, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -572,7 +571,7 @@ async function compileProgram({ methods: ((...args: any) => void)[]; gates: Gate[][]; proofSystemTag: { name: string }; - storable: Storable; + cache: Cache; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => @@ -588,26 +587,24 @@ async function compileProgram({ let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; - let storable: Pickles.Storable = [ + let cache: Pickles.Cache = [ 0, - function read_(key, path) { + function read_(key) { // TODO sanitize program name let header = parseHeader(proofSystemTag.name, methodIntfs, key); try { - let type = proverKeyType(key); - let bytes = read(path, type); + let bytes = read(header); return MlResult.ok(decodeProverKey(key, bytes)); } catch (e: any) { return MlResult.unitError(); } }, - function write_(key, value, path) { + function write_(key, value) { // TODO sanitize program name let header = parseHeader(proofSystemTag.name, methodIntfs, key); try { - let type = proverKeyType(key); let bytes = encodeProverKey(value); - write(path, bytes, type); + write(header, bytes); return MlResult.ok(undefined); } catch (e: any) { return MlResult.unitError(); @@ -625,7 +622,7 @@ async function compileProgram({ result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), - storable, + cache, overrideWrapDomain, }); } finally { diff --git a/src/lib/storable.ts b/src/lib/storable.ts index 1f9b9b93be..45a70f021f 100644 --- a/src/lib/storable.ts +++ b/src/lib/storable.ts @@ -7,34 +7,45 @@ import { } from './util/fs.js'; import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; -export { Storable, CacheHeader, cacheHeaderVersion }; +export { Cache, CacheHeader, cacheHeaderVersion }; /** * Interface for storing and retrieving values, for caching. * `read()` and `write()` can just throw errors on failure. */ -type Storable = { +type Cache = { /** * Read a value from the cache. * - * @param key The key to read from the cache. Can safely be used as a file path. - * @param type Specifies whether the data to be read is a utf8-encoded string or raw binary data. This was added - * because node's `fs.readFileSync` returns garbage when reading string files without specifying the encoding. + * @param header A small header to identify what is read from the cache. */ - read(key: string, type: 'string' | 'bytes'): Uint8Array; + read(header: CacheHeader): Uint8Array; /** * Write a value to the cache. * - * @param key The key of the data to write to the cache. This will be used by `read()` to retrieve the data. Can safely be used as a file path. + * @param header A small header to identify what is written to the cache. This will be used by `read()` to retrieve the data. * @param value The value to write to the cache, as a byte array. - * @param type Specifies whether the value originated from a utf8-encoded string or raw binary data. */ - write(key: string, value: Uint8Array, type: 'string' | 'bytes'): void; + write(header: CacheHeader, value: Uint8Array): void; }; -const cacheHeaderVersion = 0; +const cacheHeaderVersion = 0.1; -type CommonHeader = { version: number; uniqueId: string }; +type CommonHeader = { + /** + * Header version to avoid parsing incompatible headers. + */ + version: number; + /** + * A unique identifier for the data to be read. Safe to use as a file path. + */ + uniqueId: string; + /** + * Specifies whether the data to be read is a utf8-encoded string or raw binary data. This was added + * because node's `fs.readFileSync` returns garbage when reading string files without specifying the encoding. + */ + dataType: 'string' | 'bytes'; +}; type StepKeyHeader = { kind: Kind; programId: string; @@ -46,6 +57,8 @@ type WrapKeyHeader = { kind: Kind; programId: string; hash: string }; /** * A header that is passed to the caching layer, to support richer caching strategies. + * + * Both `uniqueId` and `programId` can safely be used as a file path. */ type CacheHeader = ( | StepKeyHeader<'step-pk'> @@ -55,7 +68,7 @@ type CacheHeader = ( ) & CommonHeader; -const None: Storable = { +const None: Cache = { read() { throw Error('not available'); }, @@ -64,39 +77,39 @@ const None: Storable = { }, }; -const FileSystem = (cacheDirectory: string): Storable => ({ - read(key, type: 'string' | 'bytes') { +const FileSystem = (cacheDirectory: string): Cache => ({ + read({ uniqueId, dataType }) { if (jsEnvironment !== 'node') throw Error('file system not available'); - if (type === 'string') { - let string = readFileSync(resolve(cacheDirectory, key), 'utf8'); + if (dataType === 'string') { + let string = readFileSync(resolve(cacheDirectory, uniqueId), 'utf8'); return new TextEncoder().encode(string); } else { - let buffer = readFileSync(resolve(cacheDirectory, key)); + let buffer = readFileSync(resolve(cacheDirectory, uniqueId)); return new Uint8Array(buffer.buffer); } }, - write(key, value, type: 'string' | 'bytes') { + write({ uniqueId, dataType }, data) { if (jsEnvironment !== 'node') throw Error('file system not available'); mkdirSync(cacheDirectory, { recursive: true }); - writeFileSync(resolve(cacheDirectory, key), value, { - encoding: type === 'string' ? 'utf8' : undefined, + writeFileSync(resolve(cacheDirectory, uniqueId), data, { + encoding: dataType === 'string' ? 'utf8' : undefined, }); }, }); const FileSystemDefault = FileSystem(cacheDir('pickles')); -const Storable = { +const Cache = { /** * Store data on the file system, in a directory of your choice. * - * Note: this {@link Storable} only caches data in Node.js. + * Note: this {@link Cache} only caches data in Node.js. */ FileSystem, /** * Store data on the file system, in a standard cache directory depending on the OS. * - * Note: this {@link Storable} only caches data in Node.js. + * Note: this {@link Cache} only caches data in Node.js. */ FileSystemDefault, /** diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index b6aeccc9e3..7632c6006a 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -56,7 +56,7 @@ import { inProver, snarkContext, } from './provable-context.js'; -import { Storable } from './storable.js'; +import { Cache } from './storable.js'; // external API export { @@ -662,7 +662,7 @@ class SmartContract { * it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**, * up to several minutes if your circuit is large or your hardware is not optimal for these operations. */ - static async compile({ storable = Storable.FileSystemDefault } = {}) { + static async compile({ cache = Cache.FileSystemDefault } = {}) { let methodIntfs = this._methods ?? []; let methods = methodIntfs.map(({ methodName }) => { return ( @@ -689,7 +689,7 @@ class SmartContract { methods, gates, proofSystemTag: this, - storable, + cache, }); let verificationKey = { data: verificationKey_.data, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index e7d7f4a1ad..7f01303b74 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -637,7 +637,7 @@ declare namespace Pickles { /** * Type to configure how Pickles should cache prover keys */ - type Storable = [ + type Cache = [ _: 0, read: ( key: ProverKeys.AnyKey, @@ -684,7 +684,7 @@ declare const Pickles: { config: { publicInputSize: number; publicOutputSize: number; - storable?: Pickles.Storable; + cache?: Pickles.Cache; overrideWrapDomain?: 0 | 1 | 2; } ) => { From 267ee5283fd0d504a2c501fd8a09f2cc64ef6072 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 13:18:24 +0100 Subject: [PATCH 0423/1215] move and rename storable.ts --- src/index.ts | 2 +- src/lib/{storable.ts => proof-system/cache.ts} | 4 ++-- src/lib/proof-system/prover-keys.ts | 2 +- src/lib/proof_system.ts | 2 +- src/lib/zkapp.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/lib/{storable.ts => proof-system/cache.ts} (97%) diff --git a/src/index.ts b/src/index.ts index 257fd64de0..2a21a86906 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,7 +45,7 @@ export { Undefined, Void, } from './lib/proof_system.js'; -export { Cache } from './lib/storable.js'; +export { Cache } from './lib/proof-system/cache.js'; export { Token, diff --git a/src/lib/storable.ts b/src/lib/proof-system/cache.ts similarity index 97% rename from src/lib/storable.ts rename to src/lib/proof-system/cache.ts index 45a70f021f..764899afb7 100644 --- a/src/lib/storable.ts +++ b/src/lib/proof-system/cache.ts @@ -4,8 +4,8 @@ import { mkdirSync, resolve, cacheDir, -} from './util/fs.js'; -import { jsEnvironment } from '../bindings/crypto/bindings/env.js'; +} from '../util/fs.js'; +import { jsEnvironment } from '../../bindings/crypto/bindings/env.js'; export { Cache, CacheHeader, cacheHeaderVersion }; diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 6161d6e6dd..c81479671c 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -6,7 +6,7 @@ import { Pickles, getWasm } from '../../snarky.js'; import { VerifierIndex } from '../../bindings/crypto/bindings/kimchi-types.js'; import { getRustConversion } from '../../bindings/crypto/bindings.js'; import { MlString } from '../ml/base.js'; -import { CacheHeader, cacheHeaderVersion } from '../storable.js'; +import { CacheHeader, cacheHeaderVersion } from './cache.js'; import type { MethodInterface } from '../proof_system.js'; export { parseHeader, encodeProverKey, decodeProverKey, AnyKey, AnyValue }; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 49388e6b1c..660a3da6e0 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -28,7 +28,7 @@ import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlTuple, MlUnit } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; -import { Cache } from './storable.js'; +import { Cache } from './proof-system/cache.js'; import { decodeProverKey, encodeProverKey, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 7632c6006a..11f4978e15 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -56,7 +56,7 @@ import { inProver, snarkContext, } from './provable-context.js'; -import { Cache } from './storable.js'; +import { Cache } from './proof-system/cache.js'; // external API export { From e5be79e9f2fb694e269d1e5ce3519024b5d6d1a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 14:56:20 +0100 Subject: [PATCH 0424/1215] add canWrite bool and allow read to return undefined --- src/lib/proof-system/cache.ts | 8 +++++++- src/lib/proof_system.ts | 24 +++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 764899afb7..e0116b213e 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -19,7 +19,7 @@ type Cache = { * * @param header A small header to identify what is read from the cache. */ - read(header: CacheHeader): Uint8Array; + read(header: CacheHeader): Uint8Array | undefined; /** * Write a value to the cache. * @@ -27,6 +27,10 @@ type Cache = { * @param value The value to write to the cache, as a byte array. */ write(header: CacheHeader, value: Uint8Array): void; + /** + * Indicates whether the cache is writable. + */ + canWrite: boolean; }; const cacheHeaderVersion = 0.1; @@ -75,6 +79,7 @@ const None: Cache = { write() { throw Error('not available'); }, + canWrite: false, }; const FileSystem = (cacheDirectory: string): Cache => ({ @@ -95,6 +100,7 @@ const FileSystem = (cacheDirectory: string): Cache => ({ encoding: dataType === 'string' ? 'utf8' : undefined, }); }, + canWrite: jsEnvironment === 'node', }); const FileSystemDefault = FileSystem(cacheDir('pickles')); diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 660a3da6e0..131ec22bf6 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -562,7 +562,7 @@ async function compileProgram({ methods, gates, proofSystemTag, - cache: { read, write }, + cache, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -587,30 +587,32 @@ async function compileProgram({ let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; - let cache: Pickles.Cache = [ + let picklesCache: Pickles.Cache = [ 0, - function read_(key) { + function read_(mlHeader) { // TODO sanitize program name - let header = parseHeader(proofSystemTag.name, methodIntfs, key); + let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); try { - let bytes = read(header); - return MlResult.ok(decodeProverKey(key, bytes)); + let bytes = cache.read(header); + if (bytes === undefined) return MlResult.unitError(); + return MlResult.ok(decodeProverKey(mlHeader, bytes)); } catch (e: any) { return MlResult.unitError(); } }, - function write_(key, value) { + function write_(mlHeader, value) { + if (!cache.canWrite) return MlResult.unitError(); // TODO sanitize program name - let header = parseHeader(proofSystemTag.name, methodIntfs, key); + let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); try { let bytes = encodeProverKey(value); - write(header, bytes); + cache.write(header, bytes); return MlResult.ok(undefined); } catch (e: any) { return MlResult.unitError(); } }, - MlBool(true), + MlBool(cache.canWrite), ]; let { verificationKey, provers, verify, tag } = @@ -622,7 +624,7 @@ async function compileProgram({ result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), - cache, + cache: picklesCache, overrideWrapDomain, }); } finally { From 7c9d3c3d55c63e267a43de5b6515f7df55ed2981 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 15:48:03 +0100 Subject: [PATCH 0425/1215] revert wrong pickles bindings typing --- src/lib/proof_system.ts | 2 +- src/snarky.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 131ec22bf6..4dfb0214bf 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -624,7 +624,7 @@ async function compileProgram({ result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), publicOutputSize: publicOutputType.sizeInFields(), - cache: picklesCache, + storable: picklesCache, overrideWrapDomain, }); } finally { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 7f01303b74..761fa1c901 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -684,7 +684,7 @@ declare const Pickles: { config: { publicInputSize: number; publicOutputSize: number; - cache?: Pickles.Cache; + storable?: Pickles.Cache; overrideWrapDomain?: 0 | 1 | 2; } ) => { From ce236f934ffc3206e06f4538d0b72d2c259349f1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 16:11:31 +0100 Subject: [PATCH 0426/1215] implement non-disk-filling caching strategy --- src/lib/proof-system/cache.ts | 29 ++++++++++++++++++++++------- src/lib/proof-system/prover-keys.ts | 18 +++++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index e0116b213e..459f16d56c 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -40,6 +40,10 @@ type CommonHeader = { * Header version to avoid parsing incompatible headers. */ version: number; + /** + * An identifier that is persistent even as version of the data change. Safe to use as a file path. + */ + persistentId: string; /** * A unique identifier for the data to be read. Safe to use as a file path. */ @@ -52,12 +56,12 @@ type CommonHeader = { }; type StepKeyHeader = { kind: Kind; - programId: string; + programName: string; methodName: string; methodIndex: number; hash: string; }; -type WrapKeyHeader = { kind: Kind; programId: string; hash: string }; +type WrapKeyHeader = { kind: Kind; programName: string; hash: string }; /** * A header that is passed to the caching layer, to support richer caching strategies. @@ -83,20 +87,31 @@ const None: Cache = { }; const FileSystem = (cacheDirectory: string): Cache => ({ - read({ uniqueId, dataType }) { + read({ persistentId, uniqueId, dataType }) { if (jsEnvironment !== 'node') throw Error('file system not available'); + + // read current uniqueId, return data if it matches + let currentId = readFileSync( + resolve(cacheDirectory, `${persistentId}.header`), + 'utf8' + ); + if (currentId !== uniqueId) return undefined; + if (dataType === 'string') { - let string = readFileSync(resolve(cacheDirectory, uniqueId), 'utf8'); + let string = readFileSync(resolve(cacheDirectory, persistentId), 'utf8'); return new TextEncoder().encode(string); } else { - let buffer = readFileSync(resolve(cacheDirectory, uniqueId)); + let buffer = readFileSync(resolve(cacheDirectory, persistentId)); return new Uint8Array(buffer.buffer); } }, - write({ uniqueId, dataType }, data) { + write({ persistentId, uniqueId, dataType }, data) { if (jsEnvironment !== 'node') throw Error('file system not available'); mkdirSync(cacheDirectory, { recursive: true }); - writeFileSync(resolve(cacheDirectory, uniqueId), data, { + writeFileSync(resolve(cacheDirectory, `${persistentId}.header`), uniqueId, { + encoding: 'utf8', + }); + writeFileSync(resolve(cacheDirectory, persistentId), data, { encoding: dataType === 'string' ? 'utf8' : undefined, }); }, diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index c81479671c..5f84ad2689 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -102,7 +102,7 @@ type AnyValue = | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; function parseHeader( - programId: string, + programName: string, methods: MethodInterface[], key: AnyKey ): CacheHeader { @@ -113,13 +113,15 @@ function parseHeader( let kind = snarkKeyStringKind[key[0]]; let methodIndex = key[1][3]; let methodName = methods[methodIndex].methodName; - // TODO sanitize unique id - let uniqueId = `${kind}-${programId}-${methodIndex}-${methodName}-${hash}`; + // TODO sanitize ids + let persistentId = `${kind}-${programName}-${methodName}`; + let uniqueId = `${kind}-${programName}-${methodIndex}-${methodName}-${hash}`; return { version: cacheHeaderVersion, uniqueId, kind, - programId, + persistentId, + programName, methodName, methodIndex, hash, @@ -130,13 +132,15 @@ function parseHeader( case KeyType.WrapVerificationKey: { let kind = snarkKeyStringKind[key[0]]; let dataType = snarkKeySerializationType[key[0]]; - // TODO sanitize unique id - let uniqueId = `${kind}-${programId}-${hash}`; + // TODO sanitize ids + let persistentId = `${kind}-${programName}`; + let uniqueId = `${kind}-${programName}-${hash}`; return { version: cacheHeaderVersion, uniqueId, kind, - programId, + persistentId, + programName, hash, dataType, }; From 34a870fca089c7c74b8c235bdf17682383175041 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 16:11:45 +0100 Subject: [PATCH 0427/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 782ef8c733..9836b6ee4d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 782ef8c7339351d6c52398aa1b8fcd829ef58a1f +Subproject commit 9836b6ee4d6fe3098d1d6fb59d2b599c147200e8 From 833e634b93e827bba9b076bedc961b080f642740 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 16:26:22 +0100 Subject: [PATCH 0428/1215] sanitize ids to be filename- and url-safe --- src/lib/proof-system/prover-keys.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 5f84ad2689..22682b222d 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -113,9 +113,10 @@ function parseHeader( let kind = snarkKeyStringKind[key[0]]; let methodIndex = key[1][3]; let methodName = methods[methodIndex].methodName; - // TODO sanitize ids - let persistentId = `${kind}-${programName}-${methodName}`; - let uniqueId = `${kind}-${programName}-${methodIndex}-${methodName}-${hash}`; + let persistentId = sanitize(`${kind}-${programName}-${methodName}`); + let uniqueId = sanitize( + `${kind}-${programName}-${methodIndex}-${methodName}-${hash}` + ); return { version: cacheHeaderVersion, uniqueId, @@ -132,9 +133,8 @@ function parseHeader( case KeyType.WrapVerificationKey: { let kind = snarkKeyStringKind[key[0]]; let dataType = snarkKeySerializationType[key[0]]; - // TODO sanitize ids - let persistentId = `${kind}-${programName}`; - let uniqueId = `${kind}-${programName}-${hash}`; + let persistentId = sanitize(`${kind}-${programName}`); + let uniqueId = sanitize(`${kind}-${programName}-${hash}`); return { version: cacheHeaderVersion, uniqueId, @@ -216,6 +216,10 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { } } +function sanitize(string: string): string { + return string.toLowerCase().replace(/[^a-z0-9_-]/g, '_'); +} + const snarkKeyStringKind = { [KeyType.StepProvingKey]: 'step-pk', [KeyType.StepVerificationKey]: 'step-vk', From c95a29cb0952f4c2a0b85f584d80dbb4acb0d057 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 16:27:44 +0100 Subject: [PATCH 0429/1215] code organization tweak --- src/lib/proof-system/prover-keys.ts | 136 ++++++++++++++-------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 22682b222d..55026c2251 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -12,72 +12,6 @@ import type { MethodInterface } from '../proof_system.js'; export { parseHeader, encodeProverKey, decodeProverKey, AnyKey, AnyValue }; export type { MlWrapVerificationKey }; -// Plonk_constraint_system.Make()().t - -class MlConstraintSystem { - // opaque type -} - -// Dlog_plonk_based_keypair.Make().t - -type MlBackendKeyPair = [ - _: 0, - index: WasmIndex, - cs: MlConstraintSystem -]; - -// Snarky_keys_header.t - -type MlSnarkKeysHeader = [ - _: 0, - headerVersion: number, - kind: [_: 0, type: MlString, identifier: MlString], - constraintConstants: unknown, - commits: unknown, - length: number, - commitDate: MlString, - constraintSystemHash: MlString, - identifyingHash: MlString -]; - -// Pickles.Cache.{Step,Wrap}.Key.Proving.t - -type MlStepProvingKeyHeader = [ - _: 0, - typeEqual: number, - snarkKeysHeader: MlSnarkKeysHeader, - index: number, - constraintSystem: MlConstraintSystem -]; - -type MlStepVerificationKeyHeader = [ - _: 0, - typeEqual: number, - snarkKeysHeader: MlSnarkKeysHeader, - index: number, - digest: unknown -]; - -type MlWrapProvingKeyHeader = [ - _: 0, - typeEqual: number, - snarkKeysHeader: MlSnarkKeysHeader, - constraintSystem: MlConstraintSystem -]; - -type MlWrapVerificationKeyHeader = [ - _: 0, - typeEqual: number, - snarkKeysHeader: MlSnarkKeysHeader, - digest: unknown -]; - -// Pickles.Verification_key.t - -class MlWrapVerificationKey { - // opaque type -} - // pickles_bindings.ml, any_key enum enum KeyType { @@ -87,8 +21,6 @@ enum KeyType { WrapVerificationKey, } -// TODO better names - type AnyKey = | [KeyType.StepProvingKey, MlStepProvingKeyHeader] | [KeyType.StepVerificationKey, MlStepVerificationKeyHeader] @@ -233,3 +165,71 @@ const snarkKeySerializationType = { [KeyType.WrapProvingKey]: 'bytes', [KeyType.WrapVerificationKey]: 'string', } as const; + +// pickles types + +// Plonk_constraint_system.Make()().t + +class MlConstraintSystem { + // opaque type +} + +// Dlog_plonk_based_keypair.Make().t + +type MlBackendKeyPair = [ + _: 0, + index: WasmIndex, + cs: MlConstraintSystem +]; + +// Snarky_keys_header.t + +type MlSnarkKeysHeader = [ + _: 0, + headerVersion: number, + kind: [_: 0, type: MlString, identifier: MlString], + constraintConstants: unknown, + commits: unknown, + length: number, + commitDate: MlString, + constraintSystemHash: MlString, + identifyingHash: MlString +]; + +// Pickles.Cache.{Step,Wrap}.Key.Proving.t + +type MlStepProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + index: number, + constraintSystem: MlConstraintSystem +]; + +type MlStepVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + index: number, + digest: unknown +]; + +type MlWrapProvingKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + constraintSystem: MlConstraintSystem +]; + +type MlWrapVerificationKeyHeader = [ + _: 0, + typeEqual: number, + snarkKeysHeader: MlSnarkKeysHeader, + digest: unknown +]; + +// Pickles.Verification_key.t + +class MlWrapVerificationKey { + // opaque type +} From 29144ff3e5cd9ac8f5137da7083706ca2d0e209e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 16:48:28 +0100 Subject: [PATCH 0430/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 9836b6ee4d..7e157de114 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9836b6ee4d6fe3098d1d6fb59d2b599c147200e8 +Subproject commit 7e157de114112ead0faec5575e67a7331de7a1bc From fad0f31fdb8e6167623ded118d0ddb71f0696be9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 17:06:08 +0100 Subject: [PATCH 0431/1215] changelog --- CHANGELOG.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc3f99c09..f809dc8d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,22 +31,17 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 - - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 - - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 - - `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182 - - `Gadgets.xor()`, new provable method to support bitwise xor for native field elements. https://github.com/o1-labs/o1js/pull/1177 - - `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188 - You can use this to write ZkPrograms that handle the base case and the inductive case in the same method. ### Changed - Use cached prover keys in `compile()` when running in Node.js https://github.com/o1-labs/o1js/pull/1187 - - Caching is configurable by passing a custom `Storable` (new export) to `compile()` + - Caching is configurable by passing a custom `Cache` (new export) to `compile()` - By default, prover keys are stored in an OS-dependent cache directory; `~/.cache/pickles` on Mac and Linux ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) From 93f318a8ec7fbf0ff725744d5f75c21c9e5b67d1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 17:10:32 +0100 Subject: [PATCH 0432/1215] minor --- src/lib/proof-system/cache.ts | 2 +- src/lib/proof_system.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 459f16d56c..78afe44186 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -41,7 +41,7 @@ type CommonHeader = { */ version: number; /** - * An identifier that is persistent even as version of the data change. Safe to use as a file path. + * An identifier that is persistent even as versions of the data change. Safe to use as a file path. */ persistentId: string; /** diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 4dfb0214bf..9dfc8ce2c6 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -590,7 +590,6 @@ async function compileProgram({ let picklesCache: Pickles.Cache = [ 0, function read_(mlHeader) { - // TODO sanitize program name let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); try { let bytes = cache.read(header); @@ -602,7 +601,7 @@ async function compileProgram({ }, function write_(mlHeader, value) { if (!cache.canWrite) return MlResult.unitError(); - // TODO sanitize program name + let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); try { let bytes = encodeProverKey(value); From 132250dd3f6d05663e48f5c4d44e6f5ac984f2fe Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 18:15:55 +0100 Subject: [PATCH 0433/1215] change cache directory --- src/lib/proof-system/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 78afe44186..11e1bf0d96 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -118,7 +118,7 @@ const FileSystem = (cacheDirectory: string): Cache => ({ canWrite: jsEnvironment === 'node', }); -const FileSystemDefault = FileSystem(cacheDir('pickles')); +const FileSystemDefault = FileSystem(cacheDir('o1js')); const Cache = { /** From 953901f1640590f87b44e33b591f61a0aa85676f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 20:52:26 +0100 Subject: [PATCH 0434/1215] document prover-keys.ts better and use better names for some types --- src/lib/proof-system/prover-keys.ts | 62 ++++++++++++++++++++--------- src/snarky.d.ts | 19 ++++----- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/lib/proof-system/prover-keys.ts b/src/lib/proof-system/prover-keys.ts index 55026c2251..18b68f27b9 100644 --- a/src/lib/proof-system/prover-keys.ts +++ b/src/lib/proof-system/prover-keys.ts @@ -1,3 +1,10 @@ +/** + * This file provides helpers to + * - encode and decode all 4 kinds of snark keys to/from bytes + * - create a header which is passed to the `Cache` so that it can figure out where and if to read from cache + * + * The inputs are `SnarkKeyHeader` and `SnarkKey`, which are OCaml tagged enums defined in pickles_bindings.ml + */ import { WasmPastaFpPlonkIndex, WasmPastaFqPlonkIndex, @@ -9,11 +16,16 @@ import { MlString } from '../ml/base.js'; import { CacheHeader, cacheHeaderVersion } from './cache.js'; import type { MethodInterface } from '../proof_system.js'; -export { parseHeader, encodeProverKey, decodeProverKey, AnyKey, AnyValue }; +export { + parseHeader, + encodeProverKey, + decodeProverKey, + SnarkKeyHeader, + SnarkKey, +}; export type { MlWrapVerificationKey }; -// pickles_bindings.ml, any_key enum - +// there are 4 types of snark keys in Pickles which we all handle at once enum KeyType { StepProvingKey, StepVerificationKey, @@ -21,29 +33,32 @@ enum KeyType { WrapVerificationKey, } -type AnyKey = +type SnarkKeyHeader = | [KeyType.StepProvingKey, MlStepProvingKeyHeader] | [KeyType.StepVerificationKey, MlStepVerificationKeyHeader] | [KeyType.WrapProvingKey, MlWrapProvingKeyHeader] | [KeyType.WrapVerificationKey, MlWrapVerificationKeyHeader]; -type AnyValue = +type SnarkKey = | [KeyType.StepProvingKey, MlBackendKeyPair] | [KeyType.StepVerificationKey, VerifierIndex] | [KeyType.WrapProvingKey, MlBackendKeyPair] | [KeyType.WrapVerificationKey, MlWrapVerificationKey]; +/** + * Create `CacheHeader` from a `SnarkKeyHeader` plus some context available to `compile()` + */ function parseHeader( programName: string, methods: MethodInterface[], - key: AnyKey + header: SnarkKeyHeader ): CacheHeader { - let hash = Pickles.util.fromMlString(key[1][2][8]); - switch (key[0]) { + let hash = Pickles.util.fromMlString(header[1][2][8]); + switch (header[0]) { case KeyType.StepProvingKey: case KeyType.StepVerificationKey: { - let kind = snarkKeyStringKind[key[0]]; - let methodIndex = key[1][3]; + let kind = snarkKeyStringKind[header[0]]; + let methodIndex = header[1][3]; let methodName = methods[methodIndex].methodName; let persistentId = sanitize(`${kind}-${programName}-${methodName}`); let uniqueId = sanitize( @@ -58,13 +73,13 @@ function parseHeader( methodName, methodIndex, hash, - dataType: snarkKeySerializationType[key[0]], + dataType: snarkKeySerializationType[header[0]], }; } case KeyType.WrapProvingKey: case KeyType.WrapVerificationKey: { - let kind = snarkKeyStringKind[key[0]]; - let dataType = snarkKeySerializationType[key[0]]; + let kind = snarkKeyStringKind[header[0]]; + let dataType = snarkKeySerializationType[header[0]]; let persistentId = sanitize(`${kind}-${programName}`); let uniqueId = sanitize(`${kind}-${programName}-${hash}`); return { @@ -80,7 +95,10 @@ function parseHeader( } } -function encodeProverKey(value: AnyValue): Uint8Array { +/** + * Encode a snark key to bytes + */ +function encodeProverKey(value: SnarkKey): Uint8Array { let wasm = getWasm(); switch (value[0]) { case KeyType.StepProvingKey: { @@ -111,13 +129,16 @@ function encodeProverKey(value: AnyValue): Uint8Array { } } -function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { +/** + * Decode bytes to a snark key with the help of its header + */ +function decodeProverKey(header: SnarkKeyHeader, bytes: Uint8Array): SnarkKey { let wasm = getWasm(); - switch (key[0]) { + switch (header[0]) { case KeyType.StepProvingKey: { let srs = Pickles.loadSrsFp(); let index = wasm.caml_pasta_fp_plonk_index_decode(bytes, srs); - let cs = key[1][4]; + let cs = header[1][4]; return [KeyType.StepProvingKey, [0, index, cs]]; } case KeyType.StepVerificationKey: { @@ -134,7 +155,7 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { case KeyType.WrapProvingKey: { let srs = Pickles.loadSrsFq(); let index = wasm.caml_pasta_fq_plonk_index_decode(bytes, srs); - let cs = key[1][3]; + let cs = header[1][3]; return [KeyType.WrapProvingKey, [0, index, cs]]; } case KeyType.WrapVerificationKey: { @@ -143,11 +164,14 @@ function decodeProverKey(key: AnyKey, bytes: Uint8Array): AnyValue { return [KeyType.WrapVerificationKey, vk]; } default: - key satisfies never; + header satisfies never; throw Error('todo'); } } +/** + * Sanitize a string so that it can be used as a file name + */ function sanitize(string: string): string { return string.toLowerCase().replace(/[^a-z0-9_-]/g, '_'); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 761fa1c901..3c0419f643 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -14,7 +14,11 @@ import type { MlString, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; -import type * as ProverKeys from './lib/proof-system/prover-keys.js'; +import type { + SnarkKey, + SnarkKeyHeader, + MlWrapVerificationKey, +} from './lib/proof-system/prover-keys.js'; import { getWasm } from './bindings/js/wrapper.js'; import type { WasmFpSrs, @@ -639,13 +643,10 @@ declare namespace Pickles { */ type Cache = [ _: 0, - read: ( - key: ProverKeys.AnyKey, - path: string - ) => MlResult, + read: (header: SnarkKeyHeader, path: string) => MlResult, write: ( - key: ProverKeys.AnyKey, - value: ProverKeys.AnyValue, + header: SnarkKeyHeader, + value: SnarkKey, path: string ) => MlResult, canWrite: MlBool @@ -719,8 +720,8 @@ declare const Pickles: { */ dummyVerificationKey: () => [_: 0, data: string, hash: FieldConst]; - encodeVerificationKey: (vk: ProverKeys.MlWrapVerificationKey) => string; - decodeVerificationKey: (vk: string) => ProverKeys.MlWrapVerificationKey; + encodeVerificationKey: (vk: MlWrapVerificationKey) => string; + decodeVerificationKey: (vk: string) => MlWrapVerificationKey; proofToBase64: (proof: [0 | 1 | 2, Pickles.Proof]) => string; proofOfBase64: ( From ad44550972e085c69bee628cbcca3645fdb164f6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 20:53:30 +0100 Subject: [PATCH 0435/1215] remove noop statement --- src/examples/simple_zkapp.web.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/examples/simple_zkapp.web.ts b/src/examples/simple_zkapp.web.ts index 098d651779..60f5643b6d 100644 --- a/src/examples/simple_zkapp.web.ts +++ b/src/examples/simple_zkapp.web.ts @@ -125,7 +125,6 @@ tx = await Mina.transaction(sender, () => { }); await tx.prove(); await tx.sign([senderKey]).send(); -sender; console.log('final state: ' + zkapp.x.get()); console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); From a5fdfb2e1cc511e5415c5a7e210f7b91b37dee17 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 08:37:39 +0100 Subject: [PATCH 0436/1215] update readme-dev with new workflows --- README-dev.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README-dev.md b/README-dev.md index 2cd50c025c..e77b02b95d 100644 --- a/README-dev.md +++ b/README-dev.md @@ -11,16 +11,19 @@ npm run build ./run src/examples/api_exploration.ts ``` -## Build and run the web version +## Run examples in the browser ```sh npm install npm run build:web -npm run serve:web + +./run-in-browser.js src/examples/api_exploration.ts ``` To see the test running in a web browser, go to `http://localhost:8000/`. +Note: Some of our examples don't work on the web because they use Node.js APIs. + ## Run tests - Unit tests @@ -50,7 +53,7 @@ To see the test running in a web browser, go to `http://localhost:8000/`. ## Branch Compatibility -o1js is mostly used to write Mina Smart Contracts and must be compatible with the latest Berkeley Testnet, or soon Mainnet. +o1js is mostly used to write Mina Smart Contracts and must be compatible with the latest Berkeley Testnet, or soon Mainnet. The OCaml code is in the o1js-bindings repository, not directly in o1js. @@ -58,17 +61,20 @@ To maintain compatibility between the repositories and build o1js from the [Mina The following branches are compatible: -| repository | mina -> o1js -> o1js-bindings | -| ---------- | ------------------------------------- | -| branches | rampup -> main -> main | -| | berkeley -> berkeley -> berkeley | -| | develop -> develop -> develop | +| repository | mina -> o1js -> o1js-bindings | +| ---------- | -------------------------------- | +| branches | o1js-main -> main -> main | +| | berkeley -> berkeley -> berkeley | +| | develop -> develop -> develop | ## Run the GitHub actions locally + You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: + ``` act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN ``` + to execute the job "Build-And-Test-Server for the test type `Simple integration tests`. From 5162e280ed84fe8a10d9870527f23e87f1d745cd Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 08:45:25 +0100 Subject: [PATCH 0437/1215] package.json grooming --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 1ade0ac845..8afc4990a4 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "node": ">=16.4.0" }, "scripts": { - "type-check": "tsc --noEmit", "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", "make": "make -C ../../.. snarkyjs", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", @@ -53,15 +52,14 @@ "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", "build:docs": "npx typedoc", - "serve:web": "cp src/bindings/compiled/web_bindings/server.js src/bindings/compiled/web_bindings/index.html src/examples/simple_zkapp.js dist/web && node dist/web/server.js", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", "prepublishOnly": "npm run prepublish:web && npm run prepublish:node", "dump-vks": "./run src/examples/vk_regression.ts --bundle --dump ./src/examples/regression_test.json", "format": "prettier --write --ignore-unknown **/*", - "test": "./run-jest-tests.sh", "clean": "rimraf ./dist && rimraf ./src/bindings/compiled/_node_bindings", "clean-all": "npm run clean && rimraf ./tests/report && rimraf ./tests/test-artifacts", + "test": "./run-jest-tests.sh", "test:integration": "./run-integration-tests.sh", "test:unit": "./run-unit-tests.sh", "test:e2e": "rimraf ./tests/report && rimraf ./tests/test-artifacts && npx playwright test", From a20f367c3aefb55a3a3edffd3fb8c0dad1b8e1a5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 08:51:03 +0100 Subject: [PATCH 0438/1215] move example code from compiled bindings to examples folder --- package.json | 2 +- src/examples/plain-html/index.html | 15 +++++++++++ src/examples/plain-html/server.js | 42 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/examples/plain-html/index.html create mode 100644 src/examples/plain-html/server.js diff --git a/package.json b/package.json index 8afc4990a4..9e126225c0 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "test:integration": "./run-integration-tests.sh", "test:unit": "./run-unit-tests.sh", "test:e2e": "rimraf ./tests/report && rimraf ./tests/test-artifacts && npx playwright test", - "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2eTestsBuildHelper.js && cp -rf src/bindings/compiled/web_bindings/index.html src/bindings/compiled/web_bindings/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", + "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2eTestsBuildHelper.js && cp -rf src/examples/plain-html/index.html src/examples/plain-html/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", "e2e:run-server": "node dist/web/server.js", "e2e:install": "npx playwright install --with-deps", "e2e:show-report": "npx playwright show-report tests/report" diff --git a/src/examples/plain-html/index.html b/src/examples/plain-html/index.html new file mode 100644 index 0000000000..0678fde35a --- /dev/null +++ b/src/examples/plain-html/index.html @@ -0,0 +1,15 @@ + + + + + hello-snarkyjs + + + + +

Check out the console (F12)
+ + diff --git a/src/examples/plain-html/server.js b/src/examples/plain-html/server.js new file mode 100644 index 0000000000..dc7679626c --- /dev/null +++ b/src/examples/plain-html/server.js @@ -0,0 +1,42 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import http from 'node:http'; + +const port = 8000; +const defaultHeaders = { + 'content-type': 'text/html', + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', +}; + +const server = http.createServer(async (req, res) => { + let file = '.' + req.url; + console.log(file); + + if (file === './') file = './index.html'; + let content; + try { + content = await fs.readFile(path.resolve('./dist/web', file), 'utf8'); + } catch (err) { + res.writeHead(404, defaultHeaders); + res.write('404'); + res.end(); + return; + } + + const extension = path.basename(file).split('.').pop(); + const contentType = { + html: 'text/html', + js: 'application/javascript', + map: 'application/json', + }[extension]; + const headers = { ...defaultHeaders, 'content-type': contentType }; + + res.writeHead(200, headers); + res.write(content); + res.end(); +}); + +server.listen(port, () => { + console.log(`Server is running on: http://localhost:${port}`); +}); From 126dddeb34bd3fe842e3bb08f9adc7a86b52c5c0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 08:51:09 +0100 Subject: [PATCH 0439/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 5e5befc857..e247e50af4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5e5befc8579393dadb96be1917642f860624ed07 +Subproject commit e247e50af4c952a72b13b5a6754c6d2677d9d721 From 596952b64ff017c45cbcb79dad7918c1740b5f7c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 09:27:03 +0100 Subject: [PATCH 0440/1215] add utils to include a version number of some data type --- src/lib/proof-system/cache.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 11e1bf0d96..27344d53cb 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -7,7 +7,7 @@ import { } from '../util/fs.js'; import { jsEnvironment } from '../../bindings/crypto/bindings/env.js'; -export { Cache, CacheHeader, cacheHeaderVersion }; +export { Cache, CacheHeader, cacheHeaderVersion, withVersion }; /** * Interface for storing and retrieving values, for caching. @@ -33,7 +33,7 @@ type Cache = { canWrite: boolean; }; -const cacheHeaderVersion = 0.1; +const cacheHeaderVersion = 1; type CommonHeader = { /** @@ -62,6 +62,7 @@ type StepKeyHeader = { hash: string; }; type WrapKeyHeader = { kind: Kind; programName: string; hash: string }; +type PlainHeader = { kind: Kind }; /** * A header that is passed to the caching layer, to support richer caching strategies. @@ -73,9 +74,19 @@ type CacheHeader = ( | StepKeyHeader<'step-vk'> | WrapKeyHeader<'wrap-pk'> | WrapKeyHeader<'wrap-vk'> + | PlainHeader<'srs'> + | PlainHeader<'lagrange-basis'> ) & CommonHeader; +function withVersion( + header: Omit, + version = cacheHeaderVersion +): CacheHeader { + let uniqueId = `${header.uniqueId}-${version}`; + return { ...header, version, uniqueId } as CacheHeader; +} + const None: Cache = { read() { throw Error('not available'); From 9376a34ce68df7b96ede991dbdf4840b1051409d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 09:27:14 +0100 Subject: [PATCH 0441/1215] fixup merge --- src/lib/proof_system.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 40635f0c02..68ae82d1ca 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -620,7 +620,7 @@ async function compileProgram({ withThreadPool(async () => { let result: ReturnType; let id = snarkContext.enter({ inCompile: true }); - setSrsCache(storable); + setSrsCache(cache); try { result = Pickles.compile(MlArray.to(rules), { publicInputSize: publicInputType.sizeInFields(), From b62546b757e047ffa8208eb6508ee2e34c5d9346 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 09:30:42 +0100 Subject: [PATCH 0442/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index e81704e3aa..a27f4f0455 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e81704e3aac3b10f08724ae559e18010fa973f0c +Subproject commit a27f4f0455513e4c0013a310b0556caea63183b1 From e0126f7d7fc77ce5722d02aafdaa58a7ae6f4f22 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 11:22:20 +0100 Subject: [PATCH 0443/1215] bindings for web fix --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a27f4f0455..45dbd6e6c6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a27f4f0455513e4c0013a310b0556caea63183b1 +Subproject commit 45dbd6e6c6dcfb54baa461f7a1a5854b8fd8576c From e37b5fb938f40920e85b6f10e1c47d51aec8fe33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 13:23:41 +0100 Subject: [PATCH 0444/1215] export cache header --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 2a21a86906..59d6f5eb5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,7 +45,7 @@ export { Undefined, Void, } from './lib/proof_system.js'; -export { Cache } from './lib/proof-system/cache.js'; +export { Cache, CacheHeader } from './lib/proof-system/cache.js'; export { Token, From c07af69560b0575dfdd4a4bc2eebc22353ace6ab Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 13:24:30 +0100 Subject: [PATCH 0445/1215] add debug option to cache and encapsulate some read/write logic --- src/lib/proof-system/cache.ts | 38 +++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 27344d53cb..fe332387a6 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -7,7 +7,11 @@ import { } from '../util/fs.js'; import { jsEnvironment } from '../../bindings/crypto/bindings/env.js'; -export { Cache, CacheHeader, cacheHeaderVersion, withVersion }; +// external API +export { Cache, CacheHeader }; + +// internal API +export { readCache, writeCache, withVersion, cacheHeaderVersion }; /** * Interface for storing and retrieving values, for caching. @@ -31,6 +35,13 @@ type Cache = { * Indicates whether the cache is writable. */ canWrite: boolean; + /** + * If `isDebug` is toggled, cache read and write errors are logged to the console. + * + * By default, cache errors are silent, because they don't necessarily represent an error condition, + * but could just be a cache miss or file system permissions incompatible with writing data. + */ + debug?: boolean; }; const cacheHeaderVersion = 1; @@ -54,6 +65,7 @@ type CommonHeader = { */ dataType: 'string' | 'bytes'; }; + type StepKeyHeader = { kind: Kind; programName: string; @@ -65,7 +77,7 @@ type WrapKeyHeader = { kind: Kind; programName: string; hash: string }; type PlainHeader = { kind: Kind }; /** - * A header that is passed to the caching layer, to support richer caching strategies. + * A header that is passed to the caching layer, to support rich caching strategies. * * Both `uniqueId` and `programId` can safely be used as a file path. */ @@ -87,6 +99,28 @@ function withVersion( return { ...header, version, uniqueId } as CacheHeader; } +// default methods to interact with a cache + +function readCache(cache: Cache, header: CacheHeader) { + try { + let result = cache.read(header); + if (result === undefined && cache.debug) console.trace('Cache miss'); + return result; + } catch (e) { + if (cache.debug) console.log('Failed to read cache', e); + return undefined; + } +} + +function writeCache(cache: Cache, header: CacheHeader, value: Uint8Array) { + if (!cache.canWrite) return; + try { + cache.write(header, value); + } catch (e) { + if (cache.debug) console.log('Failed to write cache', e); + } +} + const None: Cache = { read() { throw Error('not available'); From 6aeb06882d85dd6e515afaf62bcc0bb2275a0b83 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 13:56:54 +0100 Subject: [PATCH 0446/1215] make internal cache helpers handle try-catch --- src/lib/proof-system/cache.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index fe332387a6..c3571af782 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -101,11 +101,25 @@ function withVersion( // default methods to interact with a cache -function readCache(cache: Cache, header: CacheHeader) { +function readCache(cache: Cache, header: CacheHeader): Uint8Array | undefined; +function readCache( + cache: Cache, + header: CacheHeader, + transform: (x: Uint8Array) => T +): T | undefined; +function readCache( + cache: Cache, + header: CacheHeader, + transform?: (x: Uint8Array) => T +): T | undefined { try { let result = cache.read(header); - if (result === undefined && cache.debug) console.trace('Cache miss'); - return result; + if (result === undefined) { + if (cache.debug) console.trace('cache miss'); + return undefined; + } + if (transform === undefined) return result as any as T; + return transform(result); } catch (e) { if (cache.debug) console.log('Failed to read cache', e); return undefined; @@ -113,11 +127,13 @@ function readCache(cache: Cache, header: CacheHeader) { } function writeCache(cache: Cache, header: CacheHeader, value: Uint8Array) { - if (!cache.canWrite) return; + if (!cache.canWrite) return false; try { cache.write(header, value); + return true; } catch (e) { if (cache.debug) console.log('Failed to write cache', e); + return false; } } From ee0daeb11674238df02c435c406b2e1298bde013 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 13:57:10 +0100 Subject: [PATCH 0447/1215] use internal helpers in compile --- src/lib/proof_system.ts | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 68ae82d1ca..8ef1bc9fc8 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -28,7 +28,7 @@ import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlTuple, MlUnit } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; -import { Cache } from './proof-system/cache.js'; +import { Cache, readCache, writeCache } from './proof-system/cache.js'; import { decodeProverKey, encodeProverKey, @@ -592,25 +592,20 @@ async function compileProgram({ 0, function read_(mlHeader) { let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); - try { - let bytes = cache.read(header); - if (bytes === undefined) return MlResult.unitError(); - return MlResult.ok(decodeProverKey(mlHeader, bytes)); - } catch (e: any) { - return MlResult.unitError(); - } + let result = readCache(cache, header, (bytes) => + decodeProverKey(mlHeader, bytes) + ); + if (result === undefined) return MlResult.unitError(); + return MlResult.ok(result); }, function write_(mlHeader, value) { if (!cache.canWrite) return MlResult.unitError(); let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); - try { - let bytes = encodeProverKey(value); - cache.write(header, bytes); - return MlResult.ok(undefined); - } catch (e: any) { - return MlResult.unitError(); - } + let didWrite = writeCache(cache, header, encodeProverKey(value)); + + if (!didWrite) return MlResult.unitError(); + return MlResult.ok(undefined); }, MlBool(cache.canWrite), ]; From dec1cdaece5697b80b377598993509e5582efc36 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 14:06:28 +0100 Subject: [PATCH 0448/1215] expose debug param --- src/lib/proof-system/cache.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index c3571af782..0aa99c4c3c 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -147,7 +147,7 @@ const None: Cache = { canWrite: false, }; -const FileSystem = (cacheDirectory: string): Cache => ({ +const FileSystem = (cacheDirectory: string, debug?: boolean): Cache => ({ read({ persistentId, uniqueId, dataType }) { if (jsEnvironment !== 'node') throw Error('file system not available'); @@ -177,6 +177,7 @@ const FileSystem = (cacheDirectory: string): Cache => ({ }); }, canWrite: jsEnvironment === 'node', + debug, }); const FileSystemDefault = FileSystem(cacheDir('o1js')); From 02433b2cfa4a86ff7f7223b5320db812a4aebba1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 14:29:53 +0100 Subject: [PATCH 0449/1215] document caching more --- src/lib/proof-system/cache.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system/cache.ts b/src/lib/proof-system/cache.ts index 0aa99c4c3c..42343c88fc 100644 --- a/src/lib/proof-system/cache.ts +++ b/src/lib/proof-system/cache.ts @@ -16,6 +16,17 @@ export { readCache, writeCache, withVersion, cacheHeaderVersion }; /** * Interface for storing and retrieving values, for caching. * `read()` and `write()` can just throw errors on failure. + * + * The data that will be passed to the cache for writing is exhaustively described by the {@link CacheHeader} type. + * It represents one of the following: + * - The SRS. This is a deterministic lists of curve points (one per curve) that needs to be generated just once, + * to be used for polynomial commitments. + * - Lagrange basis commitments. Similar to the SRS, this will be created once for every power-of-2 circuit size. + * - Prover and verifier keys for every compiled circuit. + * + * Per smart contract or ZkProgram, several different keys are created: + * - a step prover key (`step-pk`) and verification key (`step-vk`) _for every method_. + * - a wrap prover key (`wrap-pk`) and verification key (`wrap-vk`) for the entire contract. */ type Cache = { /** @@ -24,6 +35,7 @@ type Cache = { * @param header A small header to identify what is read from the cache. */ read(header: CacheHeader): Uint8Array | undefined; + /** * Write a value to the cache. * @@ -31,15 +43,17 @@ type Cache = { * @param value The value to write to the cache, as a byte array. */ write(header: CacheHeader, value: Uint8Array): void; + /** * Indicates whether the cache is writable. */ canWrite: boolean; + /** - * If `isDebug` is toggled, cache read and write errors are logged to the console. + * If `debug` is toggled, `read()` and `write()` errors are logged to the console. * * By default, cache errors are silent, because they don't necessarily represent an error condition, - * but could just be a cache miss or file system permissions incompatible with writing data. + * but could just be a cache miss, or file system permissions incompatible with writing data. */ debug?: boolean; }; @@ -186,12 +200,18 @@ const Cache = { /** * Store data on the file system, in a directory of your choice. * + * Data will be stored in two files per cache entry: a data file and a `.header` file. + * The header file just contains a unique string which is used to determine whether we can use the cached data. + * * Note: this {@link Cache} only caches data in Node.js. */ FileSystem, /** * Store data on the file system, in a standard cache directory depending on the OS. * + * Data will be stored in two files per cache entry: a data file and a `.header` file. + * The header file just contains a unique string which is used to determine whether we can use the cached data. + * * Note: this {@link Cache} only caches data in Node.js. */ FileSystemDefault, From b253629d78de090858572ea151609d1ba02b90a7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 14:30:15 +0100 Subject: [PATCH 0450/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 45dbd6e6c6..03cc35851b 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 45dbd6e6c6dcfb54baa461f7a1a5854b8fd8576c +Subproject commit 03cc35851bb753500793b63a90f72b2131fae95c From 67ea2f7ab68016eeb24c040c9a13cb3f651ac02a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 14:32:54 +0100 Subject: [PATCH 0451/1215] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f809dc8d47..d97e6b1ead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Use cached prover keys in `compile()` when running in Node.js https://github.com/o1-labs/o1js/pull/1187 - Caching is configurable by passing a custom `Cache` (new export) to `compile()` - By default, prover keys are stored in an OS-dependent cache directory; `~/.cache/pickles` on Mac and Linux +- Use cached setup points (SRS and Lagrange bases) when running in Node.js https://github.com/o1-labs/o1js/pull/1197 + - Also, speed up SRS generation by using multiple threads + - Together with caching of prover keys, this speeds up compilation time by roughly + - **86%** when everything is cached + - **34%** when nothing is cached ## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7) From 8d803a20c66127f8f9ba7ed13b6a39f37da284d0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 15:11:52 +0100 Subject: [PATCH 0452/1215] changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d97e6b1ead..2588d44c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/045faa7...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/e8e7510e1...HEAD) + +> No unreleased changes yet + +## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) ### Breaking changes From 8bd47f1d881e8658b463cd35cfd024739a5b81bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 31 Oct 2023 15:12:01 +0100 Subject: [PATCH 0453/1215] 0.14.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8dc3eebf8..640490462f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.13.1", + "version": "0.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.13.1", + "version": "0.14.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 6367d83de1..7fadc8b5f5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.13.1", + "version": "0.14.0", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From 6ace3a1d5012a60b415df58649200d16b1b7ca97 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:13:28 +0000 Subject: [PATCH 0454/1215] Use infix bitwise operators --- src/lib/gadgets/bitwise.ts | 9 ++++----- src/lib/gadgets/bitwise.unit-test.ts | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 3cf33abc1e..eed902df2e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,5 +1,4 @@ import { Provable } from '../provable.js'; -import { Field as Fp } from '../../provable/field-bigint.js'; import { Field, FieldConst } from '../field.js'; import * as Gates from '../gates.js'; @@ -33,13 +32,13 @@ function xor(a: Field, b: Field, length: number) { `${b.toBigInt()} does not fit into ${padLength} bits` ); - return new Field(Fp.xor(a.toBigInt(), b.toBigInt())); + return new Field(a.toBigInt() ^ b.toBigInt()); } // calculate expect xor output let outputXor = Provable.witness( Field, - () => new Field(Fp.xor(a.toBigInt(), b.toBigInt())) + () => new Field(a.toBigInt() ^ b.toBigInt()) ); // builds the xor gadget chain @@ -139,13 +138,13 @@ function and(a: Field, b: Field, length: number) { `${b.toBigInt()} does not fit into ${padLength} bits` ); - return new Field(Fp.and(a.toBigInt(), b.toBigInt())); + return new Field(a.toBigInt() & b.toBigInt()); } // calculate expect and output let outputAnd = Provable.witness( Field, - () => new Field(Fp.and(a.toBigInt(), b.toBigInt())) + () => new Field(a.toBigInt() & b.toBigInt()) ); // compute values for gate diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 5fb6073646..dab397e558 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -6,7 +6,7 @@ import { field, fieldWithRng, } from '../testing/equivalent.js'; -import { Fp, mod } from '../../bindings/crypto/finite_field.js'; +import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; @@ -35,11 +35,11 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); [2, 4, 8, 16, 32, 64, 128].forEach((length) => { equivalent({ from: [uint(length), uint(length)], to: field })( - Fp.xor, + (x, y) => x ^ y, (x, y) => Gadgets.xor(x, y, length) ); equivalent({ from: [uint(length), uint(length)], to: field })( - Fp.and, + (x, y) => x & y, (x, y) => Gadgets.and(x, y, length) ); }); @@ -59,7 +59,7 @@ await equivalentAsync( (x, y) => { if (x >= 2n ** 64n || y >= 2n ** 64n) throw Error('Does not fit into 64 bits'); - return Fp.xor(x, y); + return x ^ y; }, async (x, y) => { let proof = await Bitwise.xor(x, y); @@ -74,7 +74,7 @@ await equivalentAsync( (x, y) => { if (x >= 2n ** 64n || y >= 2n ** 64n) throw Error('Does not fit into 64 bits'); - return Fp.and(x, y); + return x & y; }, async (x, y) => { let proof = await Bitwise.and(x, y); From 833445b2bec0bf9b04fefae34d499f584241f926 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:14:26 +0000 Subject: [PATCH 0455/1215] Bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f20ab7aa7d..c76cd48e8d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f20ab7aa7de403b410ee7f34823c6d0d80736043 +Subproject commit c76cd48e8d6218d030b7ceb77603a84104deea7c From a4eabbb3f02a2a6e882a5a5696d0d44e80f97dad Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:33:29 +0000 Subject: [PATCH 0456/1215] Rename basic gate to generic --- src/lib/gadgets/bitwise.ts | 2 +- src/lib/gates.ts | 6 +++--- src/snarky.d.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index eed902df2e..31238e0448 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -153,7 +153,7 @@ function and(a: Field, b: Field, length: number) { let xor_output = xor(a, b, length); let and_output = outputAnd; - Gates.basic( + Gates.generic( FieldConst['1'], sum, FieldConst['-1'], diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 0c026a4dfe..605989be2a 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,7 @@ import { Snarky } from '../snarky.js'; import { FieldVar, FieldConst, type Field } from './field.js'; -export { rangeCheck64, xor, zero, basic }; +export { rangeCheck64, xor, zero, generic }; /** * Asserts that x is at most 64 bits @@ -84,7 +84,7 @@ function xor( /** * Generic gate */ -function basic( +function generic( sl: FieldConst, l: Field, sr: FieldConst, @@ -94,7 +94,7 @@ function basic( sm: FieldConst, sc: FieldConst ) { - Snarky.gates.basic(sl, l.value, sr, r.value, so, o.value, sm, sc); + Snarky.gates.generic(sl, l.value, sr, r.value, so, o.value, sm, sc); } function zero(a: Field, b: Field, c: Field) { diff --git a/src/snarky.d.ts b/src/snarky.d.ts index a90f5dc3d7..aec1e90211 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -327,7 +327,7 @@ declare const Snarky: { zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; - basic( + generic( sl: FieldConst, l: FieldVar, sr: FieldConst, From c048023730ff5c70e355e9564a98c7f284530f3a Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 01:13:43 +0000 Subject: [PATCH 0457/1215] Do AND in o1js instead of calling generic directly --- src/lib/gadgets/bitwise.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 31238e0448..3c55366054 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -148,21 +148,10 @@ function and(a: Field, b: Field, length: number) { ); // compute values for gate - // explanation here: https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and + // explanation: https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and let sum = a.add(b); - let xor_output = xor(a, b, length); - let and_output = outputAnd; - - Gates.generic( - FieldConst['1'], - sum, - FieldConst['-1'], - xor_output, - FieldConst.fromBigint(-2n), - and_output, - FieldConst['0'], - FieldConst['0'] - ); + let xorOutput = xor(a, b, length); + outputAnd.mul(2).add(xorOutput).assertEquals(sum); // return the result of the and operation return outputAnd; From b94bfd89720e32ca0d36660239e058c61a00abc2 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 07:39:52 +0000 Subject: [PATCH 0458/1215] Bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a7f73ee3a5..bc3abc3d58 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a7f73ee3a5fec7a0f4c11dd841173fef3e7c9262 +Subproject commit bc3abc3d583517bba00e4ea17a0226bd5176dfb1 From bcfe4dd97d5168d16e1008060409d955089f414b Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 08:38:11 +0000 Subject: [PATCH 0459/1215] Improve generic gate interface and doccomment --- src/lib/gates.ts | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 3ea9885fde..75f0f063e7 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -101,19 +101,36 @@ function xor( } /** - * Generic gate + * [Generic gate](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=foreignfield#double-generic-gate) + * The vanilla PLONK gate that allows us to do operations like: + * * addition of two registers (into an output register) + * * multiplication of two registers + * * equality of a register with a constant + * + * More generally, the generic gate controls the coefficients (denoted `c_`) in the equation: + * + * `c_l*l + c_r*r + c_o+o + c_m*l*r + c_c === 0` */ function generic( - sl: FieldConst, - l: Field, - sr: FieldConst, - r: Field, - so: FieldConst, - o: Field, - sm: FieldConst, - sc: FieldConst + coefficients: { + left: bigint; + right: bigint; + out: bigint; + mul: bigint; + const: bigint; + }, + inputs: { left: Field; right: Field; out: Field } ) { - Snarky.gates.generic(sl, l.value, sr, r.value, so, o.value, sm, sc); + Snarky.gates.generic( + FieldConst.fromBigint(coefficients.left), + inputs.left.value, + FieldConst.fromBigint(coefficients.right), + inputs.right.value, + FieldConst.fromBigint(coefficients.out), + inputs.out.value, + FieldConst.fromBigint(coefficients.mul), + FieldConst.fromBigint(coefficients.const) + ); } function zero(a: Field, b: Field, c: Field) { From aae706c21aab00e1c744c672e68295f1a92d00ae Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 08:39:24 +0000 Subject: [PATCH 0460/1215] Dump vks --- src/examples/regression_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 362a222f2d..8b5f3c8ba9 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -178,7 +178,7 @@ }, "and": { "rows": 19, - "digest": "f18a6905deba799225051cdd89ef2606" + "digest": "647e6fd1852873d1c326ba1cd269cff2" } }, "verificationKey": { From ba1c79bdc52fc4ee9c3c5f4e2a02771c769a1275 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 01:45:29 -0700 Subject: [PATCH 0461/1215] chore(bindings): update subproject commit hash to 9779d243c632ad699217072134618a1e29c06763 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 4d988ded11..9779d243c6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4d988ded114f7e2d3283b5ef94b2552495b26b8f +Subproject commit 9779d243c632ad699217072134618a1e29c06763 From 2dadc544e515d1b66710142d41190737f1e017c7 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 08:51:09 +0000 Subject: [PATCH 0462/1215] Fix typos in doccomment --- src/lib/gadgets/gadgets.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8c742d37a5..3ad0de8f43 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -108,16 +108,16 @@ const Gadgets = { }, /** * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). - * An AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. * - * It can be checked by a double generic gate the that verifies the following relationship between the values below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`). + * It can be checked by a double generic gate that verifies the following relationship between the values below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`). * * The generic gate verifies:\ * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ * Where:\ * `a + b = sum`\ - * `a x b = xor`\ - * `a ^ b = and` + * `a ^ b = xor`\ + * `a & b = and` * * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * @@ -125,7 +125,7 @@ const Gadgets = { * * **Note:** Specifying a larger `length` parameter adds additional constraints. * - * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated.. + * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. * * ```typescript From 2d8f9c908115bad50ad0815ede8f12644dba3478 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 01:52:23 -0700 Subject: [PATCH 0463/1215] feat(bitwise.ts): pass length parameter to Fp.not() --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index a35a34a98e..5cc66ffa05 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -32,7 +32,7 @@ function not(a: Field, length: number) { a.toBigInt() < max, `${a.toBigInt()} does not fit into ${padLength} bits` ); - return new Field(Fp.not(a.toBigInt())); + return new Field(Fp.not(a.toBigInt(), length)); } // create a bitmask with all ones From f69c9628a3e613dc1bc1392e2866261abf66b2ba Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 1 Nov 2023 09:01:06 +0000 Subject: [PATCH 0464/1215] Consolidated gadget ZkPrograms --- src/examples/gadgets.ts | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/examples/gadgets.ts b/src/examples/gadgets.ts index a84cca31b5..0a87b61dce 100644 --- a/src/examples/gadgets.ts +++ b/src/examples/gadgets.ts @@ -1,5 +1,3 @@ -import { timeEnd } from 'node:console'; -import { basename } from 'node:path'; import { Field, Provable, Gadgets, ZkProgram } from 'o1js'; let cs = Provable.constraintSystem(() => { @@ -16,10 +14,10 @@ let cs = Provable.constraintSystem(() => { }); console.log('constraint system: ', cs); -const ROT = ZkProgram({ - name: 'rot-example', +const BitwiseProver = ZkProgram({ + name: 'bitwise', methods: { - baseCase: { + rot: { privateInputs: [], method: () => { let a = Provable.witness(Field, () => Field(48)); @@ -33,13 +31,7 @@ const ROT = ZkProgram({ actualRight.assertEquals(expectedRight); }, }, - }, -}); - -const XOR = ZkProgram({ - name: 'xor-example', - methods: { - baseCase: { + xor: { privateInputs: [], method: () => { let a = Provable.witness(Field, () => Field(5)); @@ -49,13 +41,7 @@ const XOR = ZkProgram({ actual.assertEquals(expected); }, }, - }, -}); - -const AND = ZkProgram({ - name: 'and-example', - methods: { - baseCase: { + and: { privateInputs: [], method: () => { let a = Provable.witness(Field, () => Field(3)); @@ -71,24 +57,22 @@ const AND = ZkProgram({ console.log('compiling..'); console.time('compile'); -await ROT.compile(); -await XOR.compile(); -await AND.compile(); +await BitwiseProver.compile(); console.timeEnd('compile'); console.log('proving..'); console.time('rotation prove'); -let rotProof = await ROT.baseCase(); +let rotProof = await BitwiseProver.rot(); console.timeEnd('rotation prove'); -if (!(await ROT.verify(rotProof))) throw Error('rotate: Invalid proof'); +if (!(await BitwiseProver.verify(rotProof))) throw Error('rot: Invalid proof'); console.time('xor prove'); -let xorProof = await XOR.baseCase(); +let xorProof = await BitwiseProver.xor(); console.timeEnd('xor prove'); -if (!(await XOR.verify(xorProof))) throw Error('xor: Invalid proof'); +if (!(await BitwiseProver.verify(xorProof))) throw Error('xor: Invalid proof'); console.time('and prove'); -let andProof = await AND.baseCase(); +let andProof = await BitwiseProver.and(); console.timeEnd('and prove'); -if (!(await AND.verify(andProof))) throw Error('and: Invalid proof'); +if (!(await BitwiseProver.verify(andProof))) throw Error('and: Invalid proof'); From 5065ac76e707ac3852ad8ff2f0bacb0e0100e780 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 1 Nov 2023 10:37:01 +0100 Subject: [PATCH 0465/1215] Update src/lib/gates.ts --- src/lib/gates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 75f0f063e7..bfead9e82c 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -109,7 +109,7 @@ function xor( * * More generally, the generic gate controls the coefficients (denoted `c_`) in the equation: * - * `c_l*l + c_r*r + c_o+o + c_m*l*r + c_c === 0` + * `c_l*l + c_r*r + c_o*o + c_m*l*r + c_c === 0` */ function generic( coefficients: { From cfd670ec9867b3876c3e782dec67d4a1ab7b9d14 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 12:01:38 +0100 Subject: [PATCH 0466/1215] introduce generic-length ml tuple type --- src/lib/ml/base.ts | 39 ++++++++++++++++++++++++++++++++------- src/lib/ml/conversion.ts | 8 ++++---- src/lib/proof_system.ts | 12 ++++++------ src/snarky.d.ts | 11 ++++++----- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index caba0f9e8d..6885705272 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -3,7 +3,7 @@ */ export { MlArray, - MlTuple, + MlPair, MlList, MlOption, MlBool, @@ -11,11 +11,12 @@ export { MlResult, MlUnit, MlString, + MlTuple, }; // ocaml types -type MlTuple = [0, X, Y]; +type MlPair = [0, X, Y]; type MlArray = [0, ...T[]]; type MlList = [0, T, 0 | MlList]; type MlOption = 0 | [0, T]; @@ -48,18 +49,18 @@ const MlArray = { }, }; -const MlTuple = Object.assign( - function MlTuple(x: X, y: Y): MlTuple { +const MlPair = Object.assign( + function MlTuple(x: X, y: Y): MlPair { return [0, x, y]; }, { - from([, x, y]: MlTuple): [X, Y] { + from([, x, y]: MlPair): [X, Y] { return [x, y]; }, - first(t: MlTuple): X { + first(t: MlPair): X { return t[1]; }, - second(t: MlTuple): Y { + second(t: MlPair): Y { return t[2]; }, } @@ -113,3 +114,27 @@ const MlResult = { return [1, 0]; }, }; + +/** + * tuple type that has the length as generic parameter + */ +type MlTuple = N extends N + ? number extends N + ? [0, ...T[]] // N is not typed as a constant => fall back to array + : [0, ...TupleRec] + : never; + +type TupleRec = R['length'] extends N + ? R + : TupleRec; + +type Tuple = [T, ...T[]] | []; + +const MlTuple = { + map, B>( + [, ...mlTuple]: [0, ...T], + f: (a: T[number]) => B + ): [0, ...{ [i in keyof T]: B }] { + return [0, ...mlTuple.map(f)] as any; + }, +}; diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 0ce74e727c..de65656cce 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -8,7 +8,7 @@ import { Bool, Field } from '../core.js'; import { FieldConst, FieldVar } from '../field.js'; import { Scalar, ScalarConst } from '../scalar.js'; import { PrivateKey, PublicKey } from '../signature.js'; -import { MlTuple, MlBool, MlArray } from './base.js'; +import { MlPair, MlBool, MlArray } from './base.js'; import { MlFieldConstArray } from './fields.js'; export { Ml, MlHashInput }; @@ -35,7 +35,7 @@ const Ml = { type MlHashInput = [ flag: 0, field_elements: MlArray, - packed: MlArray> + packed: MlArray> ]; const MlHashInput = { @@ -86,7 +86,7 @@ function toPrivateKey(sk: ScalarConst) { } function fromPublicKey(pk: PublicKey): MlPublicKey { - return MlTuple(pk.x.toConstant().value[1], MlBool(pk.isOdd.toBoolean())); + return MlPair(pk.x.toConstant().value[1], MlBool(pk.isOdd.toBoolean())); } function toPublicKey([, x, isOdd]: MlPublicKey): PublicKey { return PublicKey.from({ @@ -96,7 +96,7 @@ function toPublicKey([, x, isOdd]: MlPublicKey): PublicKey { } function fromPublicKeyVar(pk: PublicKey): MlPublicKeyVar { - return MlTuple(pk.x.value, pk.isOdd.toField().value); + return MlPair(pk.x.value, pk.isOdd.toField().value); } function toPublicKeyVar([, x, isOdd]: MlPublicKeyVar): PublicKey { return PublicKey.from({ x: Field(x), isOdd: Bool(isOdd) }); diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 8ef1bc9fc8..377daf1a82 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -25,7 +25,7 @@ import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; -import { MlArray, MlBool, MlResult, MlTuple, MlUnit } from './ml/base.js'; +import { MlArray, MlBool, MlResult, MlPair, MlUnit } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; import { Cache, readCache, writeCache } from './proof-system/cache.js'; @@ -203,14 +203,14 @@ async function verify( let output = MlFieldConstArray.to( (proof as JsonProof).publicOutput.map(Field) ); - statement = MlTuple(input, output); + statement = MlPair(input, output); } else { // proof class picklesProof = proof.proof; let type = getStatementType(proof.constructor as any); let input = toFieldConsts(type.input, proof.publicInput); let output = toFieldConsts(type.output, proof.publicOutput); - statement = MlTuple(input, output); + statement = MlPair(input, output); } return prettifyStacktracePromise( withThreadPool(() => @@ -361,7 +361,7 @@ function ZkProgram< } finally { snarkContext.leave(id); } - let [publicOutputFields, proof] = MlTuple.from(result); + let [publicOutputFields, proof] = MlPair.from(result); let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields); class ProgramProof extends Proof { static publicInputType = publicInputType; @@ -397,7 +397,7 @@ function ZkProgram< `Cannot verify proof, verification key not found. Try calling \`await program.compile()\` first.` ); } - let statement = MlTuple( + let statement = MlPair( toFieldConsts(publicInputType, proof.publicInput), toFieldConsts(publicOutputType, proof.publicOutput) ); @@ -714,7 +714,7 @@ function picklesRuleFromFunction( proofs.push(proofInstance); let input = toFieldVars(type.input, publicInput); let output = toFieldVars(type.output, publicOutput); - previousStatements.push(MlTuple(input, output)); + previousStatements.push(MlPair(input, output)); } else if (arg.type === 'generic') { finalArgs[i] = argsWithoutPublicInput?.[i] ?? emptyGeneric(); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d40bf53ec0..0c4e1202e3 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -4,7 +4,7 @@ import type { BoolVar, Bool } from './lib/bool.js'; import type { ScalarConst } from './lib/scalar.js'; import type { MlArray, - MlTuple, + MlPair, MlList, MlOption, MlBool, @@ -12,6 +12,7 @@ import type { MlResult, MlUnit, MlString, + MlTuple, } from './lib/ml/base.js'; import type { MlHashInput } from './lib/ml/conversion.js'; import type { @@ -155,7 +156,7 @@ declare interface ProvablePure extends Provable { check: (value: T) => void; } -type MlGroup = MlTuple; +type MlGroup = MlPair; declare namespace Snarky { type Main = (publicInput: MlArray) => void; @@ -290,7 +291,7 @@ declare const Snarky: { ): [ _: 0, constant: MlOption, - terms: MlList> + terms: MlList> ]; }; @@ -435,7 +436,7 @@ declare const Snarky: { input: MlArray ): [0, FieldVar, FieldVar, FieldVar]; - hashToGroup(input: MlArray): MlTuple; + hashToGroup(input: MlArray): MlPair; sponge: { create(isChecked: boolean): unknown; @@ -540,7 +541,7 @@ declare const Test: { }; poseidon: { - hashToGroup(input: MlArray): MlTuple; + hashToGroup(input: MlArray): MlPair; }; signature: { From f5d461b21a083deae939da45b580e7c023f76cda Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 12:02:45 +0100 Subject: [PATCH 0467/1215] expose range check 1 --- src/bindings | 2 +- src/snarky.d.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index bc3abc3d58..820eba0641 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit bc3abc3d583517bba00e4ea17a0226bd5176dfb1 +Subproject commit 820eba0641ccc789268ffc6a31159ff675214554 diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 0c4e1202e3..865338d86d 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -321,6 +321,15 @@ declare const Snarky: { compact: FieldConst ): void; + rangeCheck1( + v2: FieldVar, + v12: FieldVar, + v0p: MlTuple, + v1p: MlTuple, + v2p: MlTuple, + v2c: MlTuple + ): void; + rotate( field: FieldVar, rotated: FieldVar, From ff26ac685b9e2e1cea7bac5b29d40359dd79452b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 12:05:54 +0100 Subject: [PATCH 0468/1215] use mltuple for range check 0 --- src/snarky.d.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 865338d86d..a0292a62dc 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -306,18 +306,8 @@ declare const Snarky: { */ rangeCheck0( v0: FieldVar, - v0p: [0, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar, FieldVar], - v0c: [ - 0, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar, - FieldVar - ], + v0p: MlTuple, + v0c: MlTuple, compact: FieldConst ): void; From ea9da10b223dd57301bc082669afd225908ccadc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 12:21:39 +0100 Subject: [PATCH 0469/1215] move ecAdd to gates and reorder gates --- src/bindings | 2 +- src/lib/group.ts | 2 +- src/snarky.d.ts | 54 ++++++++++++++++++++++++------------------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bindings b/src/bindings index 820eba0641..d59d34841d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 820eba0641ccc789268ffc6a31159ff675214554 +Subproject commit d59d34841d59536f78e3b48d88f10db754578c8c diff --git a/src/lib/group.ts b/src/lib/group.ts index 552a8263ad..89cf5bf249 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -170,7 +170,7 @@ class Group { return s.mul(x1.sub(x3)).sub(y1); }); - let [, x, y] = Snarky.group.ecadd( + let [, x, y] = Snarky.gates.ecAdd( Group.from(x1.seal(), y1.seal()).#toTuple(), Group.from(x2.seal(), y2.seal()).#toTuple(), Group.from(x3, y3).#toTuple(), diff --git a/src/snarky.d.ts b/src/snarky.d.ts index a0292a62dc..afe2fa0904 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -296,6 +296,33 @@ declare const Snarky: { }; gates: { + zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; + + generic( + sl: FieldConst, + l: FieldVar, + sr: FieldConst, + r: FieldVar, + so: FieldConst, + o: FieldVar, + sm: FieldConst, + sc: FieldConst + ): void; + + /** + * Low-level Elliptic Curve Addition gate. + */ + ecAdd( + p1: MlGroup, + p2: MlGroup, + p3: MlGroup, + inf: FieldVar, + same_x: FieldVar, + slope: FieldVar, + inf_z: FieldVar, + x21_inv: FieldVar + ): MlGroup; + /** * Range check gate * @@ -346,19 +373,6 @@ declare const Snarky: { out_2: FieldVar, out_3: FieldVar ): void; - - zero(in1: FieldVar, in2: FieldVar, out: FieldVar): void; - - generic( - sl: FieldConst, - l: FieldVar, - sr: FieldConst, - r: FieldVar, - so: FieldConst, - o: FieldVar, - sm: FieldConst, - sc: FieldConst - ): void; }; bool: { @@ -374,20 +388,6 @@ declare const Snarky: { }; group: { - /** - * Low-level Elliptic Curve Addition gate. - */ - ecadd( - p1: MlGroup, - p2: MlGroup, - p3: MlGroup, - inf: FieldVar, - same_x: FieldVar, - slope: FieldVar, - inf_z: FieldVar, - x21_inv: FieldVar - ): MlGroup; - scale(p: MlGroup, s: MlArray): MlGroup; }; From 597f3eac821f9bd30fd739c03b3e059f44e31288 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 14:16:01 +0100 Subject: [PATCH 0470/1215] bindings to expose all remaining gates --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index d59d34841d..368333c0ea 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d59d34841d59536f78e3b48d88f10db754578c8c +Subproject commit 368333c0eabfc0757798f4542577426fc835c25a From 0a818061bc149e499c00a9c50429e1c6c895805e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 14:16:43 +0100 Subject: [PATCH 0471/1215] expose all remaining gates --- src/snarky.d.ts | 136 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 9 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index afe2fa0904..d592419844 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -309,6 +309,8 @@ declare const Snarky: { sc: FieldConst ): void; + poseidon(state: MlArray>): void; + /** * Low-level Elliptic Curve Addition gate. */ @@ -323,6 +325,68 @@ declare const Snarky: { x21_inv: FieldVar ): MlGroup; + ecScale( + state: MlArray< + [ + _: 0, + accs: MlArray>, + bits: MlArray, + ss: MlArray, + base: MlGroup, + nPrev: Field, + nNext: Field + ] + > + ): void; + + ecEndoscale( + state: MlArray< + [ + _: 0, + xt: FieldVar, + yt: FieldVar, + xp: FieldVar, + yp: FieldVar, + nAcc: FieldVar, + xr: FieldVar, + yr: FieldVar, + s1: FieldVar, + s3: FieldVar, + b1: FieldVar, + b2: FieldVar, + b3: FieldVar, + b4: FieldVar + ] + >, + xs: FieldVar, + ys: FieldVar, + nAcc: FieldVar + ): void; + + ecEndoscalar( + state: MlArray< + [ + _: 0, + n0: FieldVar, + n8: FieldVar, + a0: FieldVar, + b0: FieldVar, + a8: FieldVar, + b8: FieldVar, + x0: FieldVar, + x1: FieldVar, + x2: FieldVar, + x3: FieldVar, + x4: FieldVar, + x5: FieldVar, + x6: FieldVar, + x7: FieldVar + ] + > + ): void; + + lookup(input: MlTuple): void; + /** * Range check gate * @@ -347,15 +411,6 @@ declare const Snarky: { v2c: MlTuple ): void; - rotate( - field: FieldVar, - rotated: FieldVar, - excess: FieldVar, - limbs: MlArray, - crumbs: MlArray, - two_to_rot: FieldConst - ): void; - xor( in1: FieldVar, in2: FieldVar, @@ -373,6 +428,48 @@ declare const Snarky: { out_2: FieldVar, out_3: FieldVar ): void; + + foreignFieldAdd( + left: MlTuple, + right: MlTuple, + fieldOverflow: FieldVar, + carry: FieldVar, + foreignFieldModulus: MlTuple, + sign: FieldConst + ): void; + + foreignFieldMul( + left: MlTuple, + right: MlTuple, + remainder: MlTuple, + quotient: MlTuple, + quotientHiBound: FieldVar, + product1: MlTuple, + carry0: FieldVar, + carry1p: MlTuple, + carry1c: MlTuple, + foreignFieldModulus2: FieldConst, + negForeignFieldModulus: MlTuple + ): void; + + rotate( + field: FieldVar, + rotated: FieldVar, + excess: FieldVar, + limbs: MlArray, + crumbs: MlArray, + two_to_rot: FieldConst + ): void; + + addFixedLookupTable(id: number, data: MlArray>): void; + + addRuntimeTableConfig(id: number, firstColumn: MlArray): void; + + raw( + kind: KimchiGateType, + values: MlArray, + coefficients: MlArray + ): void; }; bool: { @@ -445,6 +542,27 @@ declare const Snarky: { }; }; +declare enum KimchiGateType { + Zero, + Generic, + Poseidon, + CompleteAdd, + VarBaseMul, + EndoMul, + EndoMulScalar, + Lookup, + CairoClaim, + CairoInstruction, + CairoFlags, + CairoTransition, + RangeCheck0, + RangeCheck1, + ForeignFieldAdd, + ForeignFieldMul, + Xor16, + Rot64, +} + type GateType = | 'Zero' | 'Generic' From a49e93bbe7ab4ebe37a029c42169ff2a912630fc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 15:12:04 +0100 Subject: [PATCH 0472/1215] add nicely-typed low-level exists version --- src/lib/gadgets/common.ts | 17 ++++++++++++++- src/lib/ml/base.ts | 16 ++++++++++++++ src/lib/util/types.ts | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/lib/util/types.ts diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index cade7e3417..282a2b16b2 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,16 +1,31 @@ import { Provable } from '../provable.js'; -import { Field } from '../field.js'; +import { Field, FieldConst } from '../field.js'; +import { TupleN } from '../util/types.js'; +import { Snarky } from '../../snarky.js'; +import { MlArray } from '../ml/base.js'; const MAX_BITS = 64 as const; export { MAX_BITS, + exists, assert, witnessSlices, witnessNextValue, divideWithRemainder, }; +function exists TupleN>( + n: N, + compute: C +) { + let varsMl = Snarky.exists(n, () => + MlArray.mapTo(compute(), FieldConst.fromBigint) + ); + let vars = MlArray.mapFrom(varsMl, (v) => new Field(v)); + return TupleN.fromArray(n, vars); +} + function assert(stmt: boolean, message?: string) { if (!stmt) { throw Error(message ?? 'Assertion failed'); diff --git a/src/lib/ml/base.ts b/src/lib/ml/base.ts index 6885705272..33cdb00975 100644 --- a/src/lib/ml/base.ts +++ b/src/lib/ml/base.ts @@ -1,3 +1,5 @@ +import { TupleN } from '../util/types.js'; + /** * This module contains basic methods for interacting with OCaml */ @@ -137,4 +139,18 @@ const MlTuple = { ): [0, ...{ [i in keyof T]: B }] { return [0, ...mlTuple.map(f)] as any; }, + + mapFrom( + [, ...mlTuple]: MlTuple, + f: (a: T) => B + ): B[] { + return mlTuple.map(f); + }, + + mapTo | TupleN, B>( + tuple: T, + f: (a: T[number]) => B + ): [0, ...{ [i in keyof T]: B }] { + return [0, ...tuple.map(f)] as any; + }, }; diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts new file mode 100644 index 0000000000..dad0611f4a --- /dev/null +++ b/src/lib/util/types.ts @@ -0,0 +1,44 @@ +import { assert } from '../errors.js'; + +export { Tuple, TupleN }; + +type Tuple = [T, ...T[]] | []; + +const Tuple = { + map, B>( + tuple: T, + f: (a: T[number]) => B + ): [...{ [i in keyof T]: B }] { + return tuple.map(f) as any; + }, +}; + +/** + * tuple type that has the length as generic parameter + */ +type TupleN = N extends N + ? number extends N + ? [...T[]] // N is not typed as a constant => fall back to array + : [...TupleRec] + : never; + +const TupleN = { + map( + tuple: TupleN, + f: (a: T) => S + ): TupleN { + return tuple.map(f) as any; + }, + + fromArray(n: N, arr: T[]): TupleN { + assert( + arr.length === n, + `Expected array of length ${n}, got ${arr.length}` + ); + return arr as any; + }, +}; + +type TupleRec = R['length'] extends N + ? R + : TupleRec; From 0b38e98cea42c453a09269b7100643c5801fcf4a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 15:15:43 +0100 Subject: [PATCH 0473/1215] move range check gadget 64 code to gadgets --- src/lib/gadgets/common.ts | 5 +++ src/lib/gadgets/range-check.ts | 39 +++++++++++++++++++++-- src/lib/gates.ts | 56 ++++++++-------------------------- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 282a2b16b2..da021150b4 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -10,6 +10,7 @@ export { MAX_BITS, exists, assert, + bitSlice, witnessSlices, witnessNextValue, divideWithRemainder, @@ -32,6 +33,10 @@ function assert(stmt: boolean, message?: string) { } } +function bitSlice(x: bigint, start: number, length: number) { + return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n); +} + function witnessSlices(f: Field, start: number, length: number) { if (length <= 0) throw Error('Length must be a positive number'); diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index d27d4807a4..9a271c24af 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,5 +1,6 @@ -import { type Field } from '../field.js'; +import { Field } from '../field.js'; import * as Gates from '../gates.js'; +import { bitSlice, exists } from './common.js'; export { rangeCheck64 }; @@ -11,7 +12,39 @@ function rangeCheck64(x: Field) { if (x.toBigInt() >= 1n << 64n) { throw Error(`rangeCheck64: expected field to fit in 64 bits, got ${x}`); } - } else { - Gates.rangeCheck64(x); + return; } + + // crumbs (2-bit limbs) + let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, 2), + bitSlice(xx, 2, 2), + bitSlice(xx, 4, 2), + bitSlice(xx, 6, 2), + bitSlice(xx, 8, 2), + bitSlice(xx, 10, 2), + bitSlice(xx, 12, 2), + bitSlice(xx, 14, 2), + ]; + }); + + // 12-bit limbs + let [x16, x28, x40, x52] = exists(4, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 16, 12), + bitSlice(xx, 28, 12), + bitSlice(xx, 40, 12), + bitSlice(xx, 52, 12), + ]; + }); + + Gates.rangeCheck0( + x, + [new Field(0), new Field(0), x52, x40, x28, x16], + [x14, x12, x10, x8, x6, x4, x2, x0], + false // not using compact mode + ); } diff --git a/src/lib/gates.ts b/src/lib/gates.ts index bfead9e82c..7dd88ae712 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,45 +1,21 @@ import { Snarky } from '../snarky.js'; -import { FieldVar, FieldConst, type Field } from './field.js'; -import { MlArray } from './ml/base.js'; +import { FieldConst, type Field } from './field.js'; +import { MlArray, MlTuple } from './ml/base.js'; +import { TupleN } from './util/types.js'; -export { rangeCheck64, xor, zero, rotate, generic }; +export { rangeCheck0, xor, zero, rotate, generic }; -/** - * Asserts that x is at most 64 bits - */ -function rangeCheck64(x: Field) { - let [, x0, x2, x4, x6, x8, x10, x12, x14] = Snarky.exists(8, () => { - let xx = x.toBigInt(); - // crumbs (2-bit limbs) - return [ - 0, - getBits(xx, 0, 2), - getBits(xx, 2, 2), - getBits(xx, 4, 2), - getBits(xx, 6, 2), - getBits(xx, 8, 2), - getBits(xx, 10, 2), - getBits(xx, 12, 2), - getBits(xx, 14, 2), - ]; - }); - // 12-bit limbs - let [, x16, x28, x40, x52] = Snarky.exists(4, () => { - let xx = x.toBigInt(); - return [ - 0, - getBits(xx, 16, 12), - getBits(xx, 28, 12), - getBits(xx, 40, 12), - getBits(xx, 52, 12), - ]; - }); +function rangeCheck0( + x: Field, + xLimbs12: TupleN, + xLimbs2: TupleN, + isCompact: boolean +) { Snarky.gates.rangeCheck0( x.value, - [0, FieldVar[0], FieldVar[0], x52, x40, x28, x16], - [0, x14, x12, x10, x8, x6, x4, x2, x0], - // not using compact mode - FieldConst[0] + MlTuple.mapTo(xLimbs12, (x) => x.value), + MlTuple.mapTo(xLimbs2, (x) => x.value), + isCompact ? FieldConst[1] : FieldConst[0] ); } @@ -136,9 +112,3 @@ function generic( function zero(a: Field, b: Field, c: Field) { Snarky.gates.zero(a.value, b.value, c.value); } - -function getBits(x: bigint, start: number, length: number) { - return FieldConst.fromBigint( - (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n) - ); -} From 54bc51e552952d7f8d340c8c4cae1d0c7ff7fe3e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 15:19:23 +0100 Subject: [PATCH 0474/1215] it's just one slice --- src/lib/gadgets/bitwise.ts | 50 +++++++++++++++++++------------------- src/lib/gadgets/common.ts | 4 +-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index f88ad1e598..7366d89e09 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,7 +5,7 @@ import * as Gates from '../gates.js'; import { MAX_BITS, assert, - witnessSlices, + witnessSlice, witnessNextValue, divideWithRemainder, } from './common.js'; @@ -66,22 +66,22 @@ function buildXor( while (padLength !== 0) { // slices the inputs into 4x 4bit-sized chunks // slices of a - let in1_0 = witnessSlices(a, 0, 4); - let in1_1 = witnessSlices(a, 4, 4); - let in1_2 = witnessSlices(a, 8, 4); - let in1_3 = witnessSlices(a, 12, 4); + let in1_0 = witnessSlice(a, 0, 4); + let in1_1 = witnessSlice(a, 4, 4); + let in1_2 = witnessSlice(a, 8, 4); + let in1_3 = witnessSlice(a, 12, 4); // slices of b - let in2_0 = witnessSlices(b, 0, 4); - let in2_1 = witnessSlices(b, 4, 4); - let in2_2 = witnessSlices(b, 8, 4); - let in2_3 = witnessSlices(b, 12, 4); + let in2_0 = witnessSlice(b, 0, 4); + let in2_1 = witnessSlice(b, 4, 4); + let in2_2 = witnessSlice(b, 8, 4); + let in2_3 = witnessSlice(b, 12, 4); // slices of expected output - let out0 = witnessSlices(expectedOutput, 0, 4); - let out1 = witnessSlices(expectedOutput, 4, 4); - let out2 = witnessSlices(expectedOutput, 8, 4); - let out3 = witnessSlices(expectedOutput, 12, 4); + let out0 = witnessSlice(expectedOutput, 0, 4); + let out1 = witnessSlice(expectedOutput, 4, 4); + let out2 = witnessSlice(expectedOutput, 8, 4); + let out3 = witnessSlice(expectedOutput, 12, 4); // assert that the xor of the slices is correct, 16 bit at a time Gates.xor( @@ -221,20 +221,20 @@ function rot( rotated, excess, [ - witnessSlices(bound, 52, 12), // bits 52-64 - witnessSlices(bound, 40, 12), // bits 40-52 - witnessSlices(bound, 28, 12), // bits 28-40 - witnessSlices(bound, 16, 12), // bits 16-28 + witnessSlice(bound, 52, 12), // bits 52-64 + witnessSlice(bound, 40, 12), // bits 40-52 + witnessSlice(bound, 28, 12), // bits 28-40 + witnessSlice(bound, 16, 12), // bits 16-28 ], [ - witnessSlices(bound, 14, 2), // bits 14-16 - witnessSlices(bound, 12, 2), // bits 12-14 - witnessSlices(bound, 10, 2), // bits 10-12 - witnessSlices(bound, 8, 2), // bits 8-10 - witnessSlices(bound, 6, 2), // bits 6-8 - witnessSlices(bound, 4, 2), // bits 4-6 - witnessSlices(bound, 2, 2), // bits 2-4 - witnessSlices(bound, 0, 2), // bits 0-2 + witnessSlice(bound, 14, 2), // bits 14-16 + witnessSlice(bound, 12, 2), // bits 12-14 + witnessSlice(bound, 10, 2), // bits 10-12 + witnessSlice(bound, 8, 2), // bits 8-10 + witnessSlice(bound, 6, 2), // bits 6-8 + witnessSlice(bound, 4, 2), // bits 4-6 + witnessSlice(bound, 2, 2), // bits 2-4 + witnessSlice(bound, 0, 2), // bits 0-2 ], big2PowerRot ); diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index da021150b4..6b97c6016b 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -11,7 +11,7 @@ export { exists, assert, bitSlice, - witnessSlices, + witnessSlice, witnessNextValue, divideWithRemainder, }; @@ -37,7 +37,7 @@ function bitSlice(x: bigint, start: number, length: number) { return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n); } -function witnessSlices(f: Field, start: number, length: number) { +function witnessSlice(f: Field, start: number, length: number) { if (length <= 0) throw Error('Length must be a positive number'); return Provable.witness(Field, () => { From 5b6b89e4f632bd65b01578cd4223591509918d38 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 15:20:59 +0100 Subject: [PATCH 0475/1215] fixup --- src/lib/gadgets/bitwise.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 7366d89e09..0eeffc9b52 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -9,6 +9,7 @@ import { witnessNextValue, divideWithRemainder, } from './common.js'; +import { rangeCheck64 } from './range-check.js'; export { xor, and, rotate }; @@ -239,8 +240,8 @@ function rot( big2PowerRot ); // Compute next row - Gates.rangeCheck64(shifted); + rangeCheck64(shifted); // Compute following row - Gates.rangeCheck64(excess); + rangeCheck64(excess); return [rotated, excess, shifted]; } From cd6b954d4783d50b257f0dd5ed1a5582cdf8e1f1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 16:36:13 +0100 Subject: [PATCH 0476/1215] tweak range check 1 signature --- src/snarky.d.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d592419844..0408c637d5 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -405,10 +405,8 @@ declare const Snarky: { rangeCheck1( v2: FieldVar, v12: FieldVar, - v0p: MlTuple, - v1p: MlTuple, - v2p: MlTuple, - v2c: MlTuple + vCurr: MlTuple, + vNext: MlTuple ): void; xor( From 7621ea6a8ee9fb78984c17e947031c5d92763b77 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 16:39:18 +0100 Subject: [PATCH 0477/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 368333c0ea..0823354ae6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 368333c0eabfc0757798f4542577426fc835c25a +Subproject commit 0823354ae63477bf65120854e54f594c917f7c8f From e9557c5246f22acc9a3315cbd1f29c8e9ae64e02 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 16:42:05 +0100 Subject: [PATCH 0478/1215] add multi range check gadget --- src/lib/gadgets/range-check.ts | 123 ++++++++++++++++++++++++++++++++- src/lib/gates.ts | 20 +++++- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 9a271c24af..f9476455aa 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,10 +2,10 @@ import { Field } from '../field.js'; import * as Gates from '../gates.js'; import { bitSlice, exists } from './common.js'; -export { rangeCheck64 }; +export { rangeCheck64, multiRangeCheck }; /** - * Asserts that x is in the range [0, 2^64), handles constant case + * Asserts that x is in the range [0, 2^64) */ function rangeCheck64(x: Field) { if (x.isConstant()) { @@ -48,3 +48,122 @@ function rangeCheck64(x: Field) { false // not using compact mode ); } + +/** + * Asserts that x, y, z \in [0, 2^88) + */ +function multiRangeCheck(x: Field, y: Field, z: Field) { + if (x.isConstant() && y.isConstant() && z.isConstant()) { + if ( + x.toBigInt() >= 1n << 88n || + y.toBigInt() >= 1n << 88n || + z.toBigInt() >= 1n << 88n + ) { + throw Error( + `multiRangeCheck: expected fields to fit in 88 bits, got ${x}, ${y}, ${z}` + ); + } + + let [x64, x76] = rangeCheck0Helper(x); + let [y64, y76] = rangeCheck0Helper(y); + rangeCheck1Helper({ x64, x76, y64, y76, z, yz: new Field(0) }); + } +} + +function rangeCheck0Helper(x: Field, isCompact = false): [Field, Field] { + // crumbs (2-bit limbs) + let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 0, 2), + bitSlice(xx, 2, 2), + bitSlice(xx, 4, 2), + bitSlice(xx, 6, 2), + bitSlice(xx, 8, 2), + bitSlice(xx, 10, 2), + bitSlice(xx, 12, 2), + bitSlice(xx, 14, 2), + ]; + }); + + // 12-bit limbs + let [x16, x28, x40, x52, x64, x76] = exists(6, () => { + let xx = x.toBigInt(); + return [ + bitSlice(xx, 16, 12), + bitSlice(xx, 28, 12), + bitSlice(xx, 40, 12), + bitSlice(xx, 52, 12), + bitSlice(xx, 64, 12), + bitSlice(xx, 76, 12), + ]; + }); + + Gates.rangeCheck0( + x, + [x76, x64, x52, x40, x28, x16], + [x14, x12, x10, x8, x6, x4, x2, x0], + isCompact + ); + + // the two highest 12-bit limbs are returned because another gate + // is needed to add lookups for them + return [x64, x76]; +} + +function rangeCheck1Helper(inputs: { + x64: Field; + x76: Field; + y64: Field; + y76: Field; + z: Field; + yz: Field; +}) { + let { x64, x76, y64, y76, z, yz } = inputs; + + // create limbs for current row + let [z22, z24, z26, z28, z30, z32, z34, z36, z38, z50, z62, z74, z86] = + exists(13, () => { + let zz = z.toBigInt(); + return [ + bitSlice(zz, 22, 2), + bitSlice(zz, 24, 2), + bitSlice(zz, 26, 2), + bitSlice(zz, 28, 2), + bitSlice(zz, 30, 2), + bitSlice(zz, 32, 2), + bitSlice(zz, 34, 2), + bitSlice(zz, 36, 2), + bitSlice(zz, 38, 12), + bitSlice(zz, 50, 12), + bitSlice(zz, 62, 12), + bitSlice(zz, 74, 12), + bitSlice(zz, 86, 2), + ]; + }); + + // create limbs for next row + let [z0, z2, z4, z6, z8, z10, z12, z14, z16, z18, z20] = exists(11, () => { + let zz = z.toBigInt(); + return [ + bitSlice(zz, 0, 2), + bitSlice(zz, 2, 2), + bitSlice(zz, 4, 2), + bitSlice(zz, 6, 2), + bitSlice(zz, 8, 2), + bitSlice(zz, 10, 2), + bitSlice(zz, 12, 2), + bitSlice(zz, 14, 2), + bitSlice(zz, 16, 2), + bitSlice(zz, 18, 2), + bitSlice(zz, 20, 2), + ]; + }); + + Gates.rangeCheck1( + z, + yz, + [z86, z74, z62, z50, z38, z36, z34, z32, z30, z28, z26, z24, z22], + [z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0] + ); +} diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 7dd88ae712..16bfe7e050 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -3,7 +3,7 @@ import { FieldConst, type Field } from './field.js'; import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; -export { rangeCheck0, xor, zero, rotate, generic }; +export { rangeCheck0, rangeCheck1, xor, zero, rotate, generic }; function rangeCheck0( x: Field, @@ -19,6 +19,24 @@ function rangeCheck0( ); } +/** + * the rangeCheck1 gate is used in combination with the rangeCheck0, + * for doing a 3x88-bit range check + */ +function rangeCheck1( + v2: Field, + v12: Field, + vCurr: TupleN, + vNext: TupleN +) { + Snarky.gates.rangeCheck1( + v2.value, + v12.value, + MlTuple.mapTo(vCurr, (x) => x.value), + MlTuple.mapTo(vNext, (x) => x.value) + ); +} + function rotate( field: Field, rotated: Field, From 3f93ac6066846d5ed50b853ba35aca1dbcad1d1d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 17:16:41 +0100 Subject: [PATCH 0479/1215] compact multi range check --- src/lib/gadgets/range-check.ts | 49 +++++++++++++++++++++++++++------- src/lib/util/types.ts | 8 +++--- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index f9476455aa..16ba1bd640 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,7 +2,7 @@ import { Field } from '../field.js'; import * as Gates from '../gates.js'; import { bitSlice, exists } from './common.js'; -export { rangeCheck64, multiRangeCheck }; +export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; /** * Asserts that x is in the range [0, 2^64) @@ -49,19 +49,18 @@ function rangeCheck64(x: Field) { ); } +// default bigint limb size +const L = 88n; +const twoL = 2n * L; +const lMask = (1n << L) - 1n; + /** * Asserts that x, y, z \in [0, 2^88) */ function multiRangeCheck(x: Field, y: Field, z: Field) { if (x.isConstant() && y.isConstant() && z.isConstant()) { - if ( - x.toBigInt() >= 1n << 88n || - y.toBigInt() >= 1n << 88n || - z.toBigInt() >= 1n << 88n - ) { - throw Error( - `multiRangeCheck: expected fields to fit in 88 bits, got ${x}, ${y}, ${z}` - ); + if (x.toBigInt() >> L || y.toBigInt() >> L || z.toBigInt() >> L) { + throw Error(`Expected fields to fit in ${L} bits, got ${x}, ${y}, ${z}`); } let [x64, x76] = rangeCheck0Helper(x); @@ -70,6 +69,38 @@ function multiRangeCheck(x: Field, y: Field, z: Field) { } } +/** + * Compact multi-range-check - checks + * - xy = x + 2^88*y + * - x, y, z \in [0, 2^88) + * + * Returns the full limbs x, y, z + */ +function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { + // constant case + if (xy.isConstant() && z.isConstant()) { + if (xy.toBigInt() >> twoL || z.toBigInt() >> L) { + throw Error( + `Expected fields to fit in ${twoL} and ${L} bits respectively, got ${xy}, ${z}` + ); + } + let [x, y] = splitCompactLimb(xy.toBigInt()); + return [new Field(x), new Field(y), z]; + } + + let [x, y] = exists(2, () => splitCompactLimb(xy.toBigInt())); + + let [z64, z76] = rangeCheck0Helper(z, false); + let [x64, x76] = rangeCheck0Helper(x, true); + rangeCheck1Helper({ x64: z64, x76: z76, y64: x64, y76: x76, z: y, yz: xy }); + + return [x, y, z]; +} + +function splitCompactLimb(x01: bigint): [bigint, bigint] { + return [x01 & lMask, x01 >> L]; +} + function rangeCheck0Helper(x: Field, isCompact = false): [Field, Field] { // crumbs (2-bit limbs) let [x0, x2, x4, x6, x8, x10, x12, x14] = exists(8, () => { diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index dad0611f4a..201824ec48 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -23,10 +23,10 @@ type TupleN = N extends N : never; const TupleN = { - map( - tuple: TupleN, - f: (a: T) => S - ): TupleN { + map, B>( + tuple: T, + f: (a: T[number]) => B + ): [...{ [i in keyof T]: B }] { return tuple.map(f) as any; }, From 4491f7c18c3203eea2c466d2fafc40f4a1be2994 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 17:43:59 +0100 Subject: [PATCH 0480/1215] random distribution for positive bigints --- src/lib/testing/random.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 3324908e45..1cae85806e 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -311,6 +311,7 @@ const Random = Object.assign(Random_, { uint32, uint64, biguint: biguintWithInvalid, + bignat: bignatWithInvalid, privateKey, publicKey, scalar, @@ -658,14 +659,14 @@ function int(min: number, max: number): Random { * log-uniform distribution over range [0, max] * with bias towards 0, 1, 2 */ -function nat(max: number): Random { - if (max < 0) throw Error('max < 0'); - if (max === 0) return constant(0); +function bignat(max: bigint): Random { + if (max < 0n) throw Error('max < 0'); + if (max === 0n) return constant(0n); let bits = max.toString(2).length; let bitBits = bits.toString(2).length; // set of special numbers that will appear more often in tests - let special = [0, 0, 1]; - if (max > 1) special.push(2); + let special = [0n, 0n, 1n]; + if (max > 1n) special.push(2n); let nSpecial = special.length; return { create: () => () => { @@ -681,13 +682,21 @@ function nat(max: number): Random { let bitLength = 1 + drawUniformUintBits(bitBits); if (bitLength > bits) continue; // draw number from [0, 2**bitLength); reject if > max - let n = drawUniformUintBits(bitLength); + let n = drawUniformBigUintBits(bitLength); if (n <= max) return n; } }, }; } +/** + * log-uniform distribution over range [0, max] + * with bias towards 0, 1, 2 + */ +function nat(max: number): Random { + return map(bignat(BigInt(max)), (n) => Number(n)); +} + function fraction(fixedPrecision = 3) { let denom = 10 ** fixedPrecision; if (fixedPrecision < 1) throw Error('precision must be > 1'); @@ -825,6 +834,15 @@ function biguintWithInvalid(bits: number): RandomWithInvalid { return Object.assign(valid, { invalid }); } +function bignatWithInvalid(max: bigint): RandomWithInvalid { + let valid = bignat(max); + let double = bignat(2n * max); + let negative = map(double, (uint) => -uint - 1n); + let tooLarge = map(valid, (uint) => uint + max); + let invalid = oneOf(negative, tooLarge); + return Object.assign(valid, { invalid }); +} + function fieldWithInvalid( F: typeof Field | typeof Scalar ): RandomWithInvalid { From 00814a310c50dc3d7b66e9945ce1ee96547748ec Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 17:44:07 +0100 Subject: [PATCH 0481/1215] test multi range checks --- src/lib/gadgets/gadgets.ts | 24 +++++++- src/lib/gadgets/range-check.ts | 2 +- src/lib/gadgets/range-check.unit-test.ts | 78 +++++++++++++++++++----- 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 3ad0de8f43..ab76a96bdd 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -1,7 +1,11 @@ /** * Wrapper file for various gadgets, with a namespace and doccomments. */ -import { rangeCheck64 } from './range-check.js'; +import { + compactMultiRangeCheck, + multiRangeCheck, + rangeCheck64, +} from './range-check.js'; import { rotate, xor, and } from './bitwise.js'; import { Field } from '../core.js'; @@ -139,4 +143,22 @@ const Gadgets = { and(a: Field, b: Field, length: number) { return and(a, b, length); }, + + /** + * Multi-range check + * + * TODO + */ + multiRangeCheck(x: Field, y: Field, z: Field) { + multiRangeCheck(x, y, z); + }, + + /** + * Compact multi-range check + * + * TODO + */ + compactMultiRangeCheck(xy: Field, z: Field) { + return compactMultiRangeCheck(xy, z); + }, }; diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 16ba1bd640..e64108bd49 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,7 +2,7 @@ import { Field } from '../field.js'; import * as Gates from '../gates.js'; import { bitSlice, exists } from './common.js'; -export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; +export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck, L }; /** * Asserts that x is in the range [0, 2^64) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 4466f5e187..3a732c7ca6 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -5,16 +5,23 @@ import { Spec, boolean, equivalentAsync, - field, + fieldWithRng, } from '../testing/equivalent.js'; import { Random } from '../testing/property.js'; +import { assert } from './common.js'; import { Gadgets } from './gadgets.js'; +import { L } from './range-check.js'; -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), +let uint = (n: number | bigint): Spec => { + let uint = Random.bignat((1n << BigInt(n)) - 1n); + return fieldWithRng(uint); +}; + +let maybeUint = (n: number | bigint): Spec => { + let uint = Random.bignat((1n << BigInt(n)) - 1n); + return fieldWithRng( + Random.map(Random.oneOf(uint, uint.invalid), (x) => mod(x, Field.ORDER)) + ); }; // TODO: make a ZkFunction or something that doesn't go through Pickles @@ -22,28 +29,71 @@ let maybeUint64: Spec = { // RangeCheck64 Gate // -------------------------- -let RangeCheck64 = ZkProgram({ - name: 'range-check-64', +let RangeCheck = ZkProgram({ + name: 'range-check', methods: { - run: { + check64: { privateInputs: [Field], method(x) { Gadgets.rangeCheck64(x); }, }, + checkMulti: { + privateInputs: [Field, Field, Field], + method(x, y, z) { + Gadgets.multiRangeCheck(x, y, z); + }, + }, + checkCompact: { + privateInputs: [Field, Field], + method(xy, z) { + let [x, y] = Gadgets.compactMultiRangeCheck(xy, z); + x.add(y.mul(1n << 176n)).assertEquals(xy); + }, + }, }, }); -await RangeCheck64.compile(); +await RangeCheck.compile(); // TODO: we use this as a test because there's no way to check custom gates quickly :( -await equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 3 })( + +await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( (x) => { - if (x >= 1n << 64n) throw Error('expected 64 bits'); + assert(x < 1n << 64n); return true; }, async (x) => { - let proof = await RangeCheck64.run(x); - return await RangeCheck64.verify(proof); + let proof = await RangeCheck.check64(x); + return await RangeCheck.verify(proof); + } +); + +await equivalentAsync( + { from: [maybeUint(L), uint(L), uint(L)], to: boolean }, + { runs: 3 } +)( + (x, y, z) => { + console.log(x, y, z); + assert(!(x >> L) && !(y >> L) && !(z >> L)); + return true; + }, + async (x, y, z) => { + let proof = await RangeCheck.checkMulti(x, y, z); + return await RangeCheck.verify(proof); + } +); + +await equivalentAsync( + { from: [maybeUint(2n * L), uint(L)], to: boolean }, + { runs: 3 } +)( + (xy, z) => { + assert(!(xy >> (2n * L)) && !(z >> L)); + return true; + }, + async (xy, z) => { + let proof = await RangeCheck.checkCompact(xy, z); + return await RangeCheck.verify(proof); } ); From ca1cf2789ccce341dee6ba2a0b300ab56978bff9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 18:00:51 +0100 Subject: [PATCH 0482/1215] ouch - good that we have tests --- src/lib/gadgets/range-check.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index e64108bd49..d48356d480 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -62,11 +62,12 @@ function multiRangeCheck(x: Field, y: Field, z: Field) { if (x.toBigInt() >> L || y.toBigInt() >> L || z.toBigInt() >> L) { throw Error(`Expected fields to fit in ${L} bits, got ${x}, ${y}, ${z}`); } - - let [x64, x76] = rangeCheck0Helper(x); - let [y64, y76] = rangeCheck0Helper(y); - rangeCheck1Helper({ x64, x76, y64, y76, z, yz: new Field(0) }); + return; } + + let [x64, x76] = rangeCheck0Helper(x); + let [y64, y76] = rangeCheck0Helper(y); + rangeCheck1Helper({ x64, x76, y64, y76, z, yz: new Field(0) }); } /** From c2a2e0a1c453e91467fc3b4ae610f40dc71db4da Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 18:13:27 +0100 Subject: [PATCH 0483/1215] fixup test and add cs check --- src/lib/gadgets/range-check.unit-test.ts | 38 ++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 3a732c7ca6..0796183871 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -1,6 +1,8 @@ +import type { Gate } from '../../snarky.js'; import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../../lib/core.js'; import { ZkProgram } from '../proof_system.js'; +import { Provable } from '../provable.js'; import { Spec, boolean, @@ -8,9 +10,10 @@ import { fieldWithRng, } from '../testing/equivalent.js'; import { Random } from '../testing/property.js'; -import { assert } from './common.js'; +import { assert, exists } from './common.js'; import { Gadgets } from './gadgets.js'; import { L } from './range-check.js'; +import { expect } from 'expect'; let uint = (n: number | bigint): Spec => { let uint = Random.bignat((1n << BigInt(n)) - 1n); @@ -24,6 +27,32 @@ let maybeUint = (n: number | bigint): Spec => { ); }; +// constraint system sanity check + +function csWithoutGenerics(gates: Gate[]) { + return gates.map((g) => g.type).filter((type) => type !== 'Generic'); +} + +let check64 = Provable.constraintSystem(() => { + let [x] = exists(1, () => [0n]); + Gadgets.rangeCheck64(x); +}); +let multi = Provable.constraintSystem(() => { + let [x, y, z] = exists(3, () => [0n, 0n, 0n]); + Gadgets.multiRangeCheck(x, y, z); +}); +let compact = Provable.constraintSystem(() => { + let [xy, z] = exists(2, () => [0n, 0n]); + Gadgets.compactMultiRangeCheck(xy, z); +}); + +let expectedLayout64 = ['RangeCheck0']; +let expectedLayoutMulti = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; + +expect(csWithoutGenerics(check64.gates)).toEqual(expectedLayout64); +expect(csWithoutGenerics(multi.gates)).toEqual(expectedLayoutMulti); +expect(csWithoutGenerics(compact.gates)).toEqual(expectedLayoutMulti); + // TODO: make a ZkFunction or something that doesn't go through Pickles // -------------------------- // RangeCheck64 Gate @@ -48,7 +77,7 @@ let RangeCheck = ZkProgram({ privateInputs: [Field, Field], method(xy, z) { let [x, y] = Gadgets.compactMultiRangeCheck(xy, z); - x.add(y.mul(1n << 176n)).assertEquals(xy); + x.add(y.mul(1n << L)).assertEquals(xy); }, }, }, @@ -74,8 +103,7 @@ await equivalentAsync( { runs: 3 } )( (x, y, z) => { - console.log(x, y, z); - assert(!(x >> L) && !(y >> L) && !(z >> L)); + assert(!(x >> L) && !(y >> L) && !(z >> L), 'multi: not out of range'); return true; }, async (x, y, z) => { @@ -89,7 +117,7 @@ await equivalentAsync( { runs: 3 } )( (xy, z) => { - assert(!(xy >> (2n * L)) && !(z >> L)); + assert(!(xy >> (2n * L)) && !(z >> L), 'compact: not out of range'); return true; }, async (xy, z) => { From f11d393c024ca2a697009244bf12534c27c8754d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 18:24:44 +0100 Subject: [PATCH 0484/1215] add some doccomments --- src/lib/gadgets/gadgets.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index ab76a96bdd..32ad29acb7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -145,9 +145,15 @@ const Gadgets = { }, /** - * Multi-range check + * Multi-range check. * - * TODO + * Proves that x, y, z are all in the range [0, 2^88). + * + * This takes 4 rows, so it checks 88*3/4 = 66 bits per row. This is slightly more efficient + * than 64-bit range checks, which can do 64 bits in 1 row. + * + * In particular, the 3x88-bit range check supports bigints up to 264 bits, which in turn is enough + * to support foreign field multiplication with moduli up to 2^259. */ multiRangeCheck(x: Field, y: Field, z: Field) { multiRangeCheck(x, y, z); @@ -156,7 +162,15 @@ const Gadgets = { /** * Compact multi-range check * - * TODO + * This is a variant of {@link multiRangeCheck} where the first two variables are passed in + * combined form xy = x + 2^88*y. + * + * The gadget + * - splits up xy into x and y + * - proves that xy = x + 2^88*y + * - proves that x, y, z are all in the range [0, 2^88). + * + * The split form [x, y, z] is returned. */ compactMultiRangeCheck(xy: Field, z: Field) { return compactMultiRangeCheck(xy, z); From 302add7760f97912163cc7ec609f08c46d1bc9e3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 1 Nov 2023 18:26:16 +0100 Subject: [PATCH 0485/1215] changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2588d44c1f..5df1ca6d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/e8e7510e1...HEAD) -> No unreleased changes yet +### Added + +- `Gadgets.multiRangeCheck()` and `Gadgets.compactMultiRangeCheck()`, two building blocks for non-native arithmetic with bigints of size up to 264 bits. https://github.com/o1-labs/o1js/pull/1216 ## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) From dc63daa0c84fad570c6e2e7c245ee95d6fdd16b3 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 12:21:40 -0700 Subject: [PATCH 0486/1215] test(bitwise.unit-test.ts): add unit test for Gadgets.not function to ensure correctness of bitwise NOT operation --- src/lib/gadgets/bitwise.unit-test.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 8940bb20b4..405d5a225d 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -56,6 +56,10 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); (x, y) => x & y, (x, y) => Gadgets.and(x, y, length) ); + equivalent({ from: [uint(length), uint(length)], to: field })( + (x) => Fp.not(x, length), + (x) => Gadgets.not(x, length) + ); }); test( @@ -95,24 +99,6 @@ await equivalentAsync( } ); -// not -[2, 4, 8, 16, 32, 64, 128].forEach((length) => { - equivalent({ from: [uint(length), uint(length)], to: field })(Fp.not, (x) => - Gadgets.not(x, length) - ); -}); - -await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( - (x) => { - if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); - return Fp.not(x); - }, - async (a) => { - let proof = await Bitwise.not(a); - return proof.publicOutput; - } -); - await equivalentAsync( { from: [maybeUint64, maybeUint64], to: field }, { runs: 3 } From a593aa4ac43bbe3bd616b2079d6b49c9f6f58130 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 12:33:29 -0700 Subject: [PATCH 0487/1215] feat(gadgets.ts): update example in the documentation for the `not` function to use binary inputs --- src/lib/gadgets/gadgets.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0b99ac1148..95988ddff5 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -115,18 +115,17 @@ const Gadgets = { * corresponding bit of the operand is `0`, and returning `0` if the * corresponding bit of the operand is `1`. * - * The `length` parameter lets you define how many bits to NOT. It - * defaults to the size of the field in bits ({@link Fp.sizeInBits}). + * The `length` parameter lets you define how many bits to NOT. * * **Note:** Specifying a larger `length` parameter adds additional * * * * constraints. * * @example * ```ts - * let a = Field(5); // ... 101 - * let b = not(5,3); // ... 010 + * let a = Field(0b0101); + * let b = not(a,4); // not-ing 4 bits * - * b.assertEquals(-6); + * b.assertEquals(0b1010); * ``` * * @param a - The value to apply NOT to. From 62646866dfc9d1aed8df919f42c53c0e2ff48fde Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 12:43:14 -0700 Subject: [PATCH 0488/1215] chore(bindings): update subproject commit hash to fe6d56b0712cc86492760449d501acb2ecf3c201 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f87bdbe86a..fe6d56b071 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f87bdbe86ad106edde9341747667f55d503933ae +Subproject commit fe6d56b0712cc86492760449d501acb2ecf3c201 From 15a5200d43027024b9cd3629a4be08795d1b83c1 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 12:51:18 -0700 Subject: [PATCH 0489/1215] docs(gadgets.ts): update documentation for Gadgets.bitwiseNot function to clarify behavior and mention the 'length' parameter --- src/lib/gadgets/gadgets.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 95988ddff5..0690209979 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -108,9 +108,11 @@ const Gadgets = { }, /** - * Bitwise NOT gate on {@link Field} elements. Equivalent to the [bitwise + * Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ - * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * Web/JavaScript/Reference/Operators/Bitwise_NOT). The NOT gate only operates over the amount + * of bits specified by the 'length' paramenter. + * * A NOT gate works by returning `1` in each bit position if the * corresponding bit of the operand is `0`, and returning `0` if the * corresponding bit of the operand is `1`. From 5d7b1c3f392f3245fe5b163a1fcb1adb953165d3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Nov 2023 13:04:41 -0700 Subject: [PATCH 0490/1215] chore(bindings): update subproject commit hash to af22d5bca5cc1080645f7887f684bac646d1ca1f for latest changes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 169c97fa2a..af22d5bca5 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 169c97fa2ab1ef1ee345496795a067756fd8eab2 +Subproject commit af22d5bca5cc1080645f7887f684bac646d1ca1f From 509da0d7d94ee0190c1d023443785f14c8873ce1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Nov 2023 13:16:06 -0700 Subject: [PATCH 0491/1215] chore(bitwise.unit-test): formatting --- src/lib/gadgets/bitwise.unit-test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index b5f0f3146b..323364dd76 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -9,7 +9,7 @@ import { import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; -import { Random } from '../testing/property.js'; +import { Random } from '../testing/property.js'; let maybeUint64: Spec = { ...field, @@ -63,7 +63,6 @@ let Bitwise = ZkProgram({ await Bitwise.compile(); - [2, 4, 8, 16, 32, 64, 128].forEach((length) => { equivalent({ from: [uint(length), uint(length)], to: field })( (x, y) => x ^ y, @@ -148,4 +147,4 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( let proof = await Bitwise.rightShift(x); return proof.publicOutput; } -); \ No newline at end of file +); From c40db202bf528c7a17231e7345c5229f14d64db5 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 13:27:56 -0700 Subject: [PATCH 0492/1215] feat(bitwise.ts): add optional parameter 'checked' to the 'not' function. --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index bd75b8961e..b212901184 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -12,7 +12,7 @@ import { export { xor, not, and, rotate }; -function not(a: Field, length: number) { +function not(a: Field, length: number, checked: boolean = false) { // check that input length is positive assert(length > 0, `Input length needs to be positive values.`); From f978ede71029e15ba76eb6918c40b9e64493f10c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Nov 2023 14:37:08 -0700 Subject: [PATCH 0493/1215] refactor(bitwise.unit-test): fix rotate and shift tests --- src/lib/gadgets/bitwise.unit-test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 323364dd76..8261c38545 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -72,6 +72,9 @@ await Bitwise.compile(); (x, y) => x & y, (x, y) => Gadgets.and(x, y, length) ); +}); + +[2, 4, 8, 16, 32, 64].forEach((length) => { equivalent({ from: [uint(length)], to: field })( (x) => Fp.rot(x, 12, 'left'), (x) => Gadgets.rotate(x, 12, 'left') From b6dfc80c7b58acb96aac53e699befb63217aed36 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Nov 2023 14:39:53 -0700 Subject: [PATCH 0494/1215] docs(gadgets.ts): improve readability of shift gates --- src/lib/gadgets/gadgets.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 4ebe3608cc..2680ce05ed 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -109,12 +109,15 @@ const Gadgets = { /** * Performs a left shift operation on the provided {@link Field} element. - * This operation is akin to the `<<` shift operation in JavaScript, + * This operation is similar to the `<<` shift operation in JavaScript, * where bits are shifted to the left, and the overflowing bits are discarded. * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * * **Important:** The gadgets assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the shift. * Therefore, to safely use `leftShift()`, you need to make sure that the values passed in are range checked to 64 bits. * For example, this can be done with {@link Gadgets.rangeCheck64}. * @@ -126,7 +129,7 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary - * const y = leftShift(x, 2); // left shift by 2 bits + * const y = Gadgets.leftShift(x, 2); // left shift by 2 bits * y.assertEquals(0b110000); // 48 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); @@ -142,11 +145,14 @@ const Gadgets = { * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. * The `rightShift` function utilizes the rotation method internally to implement this operation. * + * * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * * **Important:** The gadgets assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. - * Therefore, to safely use `rightShift()`, you need to make sure that the values passed in are range checked to 64 bits. - * For example, this can be done with {@link Gadgets.rangeCheck64}. + * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the shift. + * To safely use `rightShift()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to shift. * @param bits Amount of bits to shift the {@link Field} element to the right. The amount should be between 0 and 64 (or else the shift will fail). @@ -156,7 +162,7 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary - * const y = rightShift(x, 2); // right shift by 2 bits + * const y = Gadgets.rightShift(x, 2); // right shift by 2 bits * y.assertEquals(0b000011); // 3 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); @@ -188,11 +194,12 @@ const Gadgets = { * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. * + * @example * ```typescript * let a = Field(3); // ... 000011 * let b = Field(5); // ... 000101 * - * let c = and(a, b, 2); // ... 000001 + * let c = Gadgets.and(a, b, 2); // ... 000001 * c.assertEquals(1); * ``` */ From 5123593ab71f2fa27a1345d0c5148b807bed834d Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 14:55:14 -0700 Subject: [PATCH 0495/1215] chore(bindings): update subproject commit hash to cfb9bb78ec4a4f229cdd7d10ffd659afdbe7bc7f for consistency and tracking --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index fe6d56b071..cfb9bb78ec 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit fe6d56b0712cc86492760449d501acb2ecf3c201 +Subproject commit cfb9bb78ec4a4f229cdd7d10ffd659afdbe7bc7f From a63eae8475ca75d7faa919c548c49cfabac2c483 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 15:31:23 -0700 Subject: [PATCH 0496/1215] feat(regression_test.json): dump vks --- src/examples/regression_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 0c38950153..91e4f93a47 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -178,7 +178,7 @@ }, "not": { "rows": 17, - "digest": "e558102138be69839649579eb34f2fe9" + "digest": "5e01b2cad70489c7bec1546b84ac868d" }, "and": { "rows": 19, From f7c7320cd80b5e88c6124bfae562cc3c1bd2bc39 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 15:40:20 -0700 Subject: [PATCH 0497/1215] feat(bitwise.ts): introduce separate variables for checked and unchecked cases and return unchecked by default --- src/lib/gadgets/bitwise.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 0d98e04723..21cc5542df 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -13,7 +13,7 @@ import { rangeCheck64 } from './range-check.js'; export { xor, not, and, rotate }; -function not(a: Field, length: number, checked: boolean = false) { +function not(a: Field, length: number, checked: boolean = true) { // check that input length is positive assert(length > 0, `Input length needs to be positive values.`); @@ -45,9 +45,10 @@ function not(a: Field, length: number, checked: boolean = false) { allOnesF.assertEquals(allOnes); - let notOutput = xor(a, allOnes, length); + let notChecked = xor(a, allOnes, length); + let notUnchecked = allOnes.sub(a); - return notOutput; + return checked ? notChecked : notUnchecked; } function xor(a: Field, b: Field, length: number) { From b441e34704c2e4352e6d30ab7e23fd15665215e9 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 2 Nov 2023 00:15:59 +0100 Subject: [PATCH 0498/1215] ffadd gate wrapper --- src/lib/gates.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 16bfe7e050..36f2a11ced 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -3,7 +3,15 @@ import { FieldConst, type Field } from './field.js'; import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; -export { rangeCheck0, rangeCheck1, xor, zero, rotate, generic }; +export { + rangeCheck0, + rangeCheck1, + xor, + zero, + rotate, + generic, + foreignFieldAdd, +}; function rangeCheck0( x: Field, @@ -130,3 +138,38 @@ function generic( function zero(a: Field, b: Field, c: Field) { Snarky.gates.zero(a.value, b.value, c.value); } + +/** + * bigint addition which allows for field overflow and carry + * + * - `l01 + sign*r01 - overflow*f01 - carry*2^2l === r01` + * - `l2 + sign*r2 - overflow*f2 + carry === r2` + * - overflow is 0 or sign + * - carry is 0, 1 or -1 + * + * assumes that the result is placed in the first 3 cells of the next row! + */ +function foreignFieldAdd({ + left, + right, + overflow, + carry, + modulus, + sign, +}: { + left: TupleN; + right: TupleN; + overflow: Field; + carry: Field; + modulus: TupleN; + sign: 1n | -1n; +}) { + Snarky.gates.foreignFieldAdd( + MlTuple.mapTo(left, (x) => x.value), + MlTuple.mapTo(right, (x) => x.value), + overflow.value, + carry.value, + MlTuple.mapTo(modulus, FieldConst.fromBigint), + FieldConst.fromBigint(sign) + ); +} From b07b0478fc2418a8b9ee13a8a402ccb27295a27f Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 2 Nov 2023 00:55:41 +0100 Subject: [PATCH 0499/1215] write logic for single ffadd --- src/lib/gadgets/foreign-field.ts | 71 ++++++++++++++++++++++++++++++++ src/lib/gadgets/range-check.ts | 11 ++++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/lib/gadgets/foreign-field.ts diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts new file mode 100644 index 0000000000..944c1e04c3 --- /dev/null +++ b/src/lib/gadgets/foreign-field.ts @@ -0,0 +1,71 @@ +import { Field } from '../field.js'; +import { foreignFieldAdd } from '../gates.js'; +import { Tuple } from '../util/types.js'; +import { assert, exists } from './common.js'; +import { L, lMask, twoL, twoLMask } from './range-check.js'; + +type Field3 = [Field, Field, Field]; +type bigint3 = [bigint, bigint, bigint]; +type Sign = -1n | 1n; + +function sumchain(xs: Field3[], signs: Sign[], f: bigint) { + assert(xs.length === signs.length + 1, 'inputs and operators match'); + + // TODO +} + +function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint): Field3 { + let f_ = split(f); + + let [r0, r1, r2, overflow, carry] = exists(5, () => { + let x_ = bigint3(x); + let y_ = bigint3(y); + + // figure out if there's overflow + let r = collapse(x_) + sign * collapse(y_); + let overflow = 0n; + if (sign === 1n && r > f) overflow = 1n; + if (sign === -1n && r < 0n) overflow = -1n; + + // do the carry + let r01 = collapse2(x_) + sign * collapse2(y_) - overflow * collapse2(f_); + let carry = r01 >> twoL; + r01 &= twoLMask; + let [r0, r1] = split2(r01); + let r2 = x_[2] + sign * y_[2] - overflow * f_[2] + carry; + + return [r0, r1, r2, overflow, carry]; + }); + + foreignFieldAdd({ + left: x, + right: y, + overflow, + carry, + modulus: f_, + sign, + }); + + return [r0, r1, r2]; +} + +function Field3(x: bigint3): Field3 { + return Tuple.map(x, (x) => new Field(x)); +} +function bigint3(x: Field3): bigint3 { + return Tuple.map(x, (x) => x.toBigInt()); +} + +function collapse([x0, x1, x2]: bigint3) { + return x0 + (x1 << L) + (x2 << twoL); +} +function split(x: bigint): bigint3 { + return [x & lMask, (x >> L) & lMask, x >> twoL]; +} + +function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { + return x0 + (x1 << L); +} +function split2(x: bigint): [bigint, bigint] { + return [x & lMask, x >> L]; +} diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index d48356d480..f640cd56a7 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,7 +2,15 @@ import { Field } from '../field.js'; import * as Gates from '../gates.js'; import { bitSlice, exists } from './common.js'; -export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck, L }; +export { + rangeCheck64, + multiRangeCheck, + compactMultiRangeCheck, + L, + twoL, + lMask, + twoLMask, +}; /** * Asserts that x is in the range [0, 2^64) @@ -53,6 +61,7 @@ function rangeCheck64(x: Field) { const L = 88n; const twoL = 2n * L; const lMask = (1n << L) - 1n; +const twoLMask = (1n << twoL) - 1n; /** * Asserts that x, y, z \in [0, 2^88) From cf1d4bb2d3ed5ea4faeed5201438697fc33d9bcb Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 17:17:27 -0700 Subject: [PATCH 0500/1215] feat(bitwise.ts): return unchecked not value as default --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 21cc5542df..fc97425029 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -13,7 +13,7 @@ import { rangeCheck64 } from './range-check.js'; export { xor, not, and, rotate }; -function not(a: Field, length: number, checked: boolean = true) { +function not(a: Field, length: number, checked: boolean = false) { // check that input length is positive assert(length > 0, `Input length needs to be positive values.`); From 2484d8db5c5d7fe9d8645d6b5b9a7986f58b7ec7 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 17:22:03 -0700 Subject: [PATCH 0501/1215] fix(bitwise.unit-test.ts): change the number of bits used in xor tests from 64 to 255 to support larger --- src/lib/gadgets/bitwise.unit-test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 405d5a225d..4ce195f80e 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -19,13 +19,13 @@ let Bitwise = ZkProgram({ xor: { privateInputs: [Field, Field], method(a: Field, b: Field) { - return Gadgets.xor(a, b, 64); + return Gadgets.xor(a, b, 255); }, }, not: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 64); + return Gadgets.not(a, 16); }, }, and: { @@ -89,8 +89,8 @@ await equivalentAsync( { runs: 3 } )( (x, y) => { - if (x >= 2n ** 64n || y >= 2n ** 64n) - throw Error('Does not fit into 64 bits'); + if (x >= 2n ** 255n || y >= 2n ** 255n) + throw Error('Does not fit into 255 bits'); return x ^ y; }, async (x, y) => { From f6b59ded830dec2cdbe764966da3381251241ae2 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 22:56:54 -0700 Subject: [PATCH 0502/1215] feat(gadgets.ts): add documentation for the 'checked' parameter in the 'not' method --- src/lib/gadgets/gadgets.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0690209979..e7a797102e 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -122,19 +122,29 @@ const Gadgets = { * **Note:** Specifying a larger `length` parameter adds additional * * * * constraints. * + * NOT is implemented in two different ways. If the 'checked' parameter is set to 'true' + * the {@link Gadgets.xor} gadget is reusesd the with a second argument to be an + * all one bitmask the same length. This approach needs as many rows as an XOR would need + * for a single negation. If the 'checked' parameter is set to 'false' NOT is + * implementad as a subtraction of the input from the all one bitmask. This + * implementation is returned by default if no 'checked' parameter is provided. + * + *You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * * @example * ```ts * let a = Field(0b0101); - * let b = not(a,4); // not-ing 4 bits + * let b = not(a,4,false); // not-ing 4 bits * * b.assertEquals(0b1010); * ``` * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. + * @param checked - Boolean to determine if the checked or unchecked not implementation is used. */ - not(a: Field, length: number) { - return not(a, length); + not(a: Field, length: number, checked: boolean = false) { + return not(a, length, checked); }, /** * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). @@ -147,9 +157,9 @@ const Gadgets = { * Where:\ * `a + b = sum`\ * `a ^ b = xor`\ - * `a & b = and` + * `a & b = and`You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * * - * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * From 7b524369c6b61cd8d432bdedc5af5789a155af7b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 23:11:16 -0700 Subject: [PATCH 0503/1215] feat(gadgets.ts): update 'not' doc comment --- src/lib/gadgets/gadgets.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e7a797102e..13b8bb7f4f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -110,7 +110,9 @@ const Gadgets = { /** * Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ - * Web/JavaScript/Reference/Operators/Bitwise_NOT). The NOT gate only operates over the amount + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate only operates over the amount * of bits specified by the 'length' paramenter. * * A NOT gate works by returning `1` in each bit position if the @@ -119,8 +121,8 @@ const Gadgets = { * * The `length` parameter lets you define how many bits to NOT. * - * **Note:** Specifying a larger `length` parameter adds additional * * * - * constraints. + * **Note:** Specifying a larger `length` parameter adds additional constraints. + * * * NOT is implemented in two different ways. If the 'checked' parameter is set to 'true' * the {@link Gadgets.xor} gadget is reusesd the with a second argument to be an @@ -141,7 +143,8 @@ const Gadgets = { * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. - * @param checked - Boolean to determine if the checked or unchecked not implementation is used. + * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. + * It is set to false by default if no parameter is provided. */ not(a: Field, length: number, checked: boolean = false) { return not(a, length, checked); From 185ea69b194a83a5d01d5bd06421e89b12e23981 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Wed, 1 Nov 2023 23:36:19 -0700 Subject: [PATCH 0504/1215] feat(bitwise.unit-test.ts): update the bit length value passed to the Gadgets.not() method from 16 to 255 --- src/lib/gadgets/bitwise.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4ce195f80e..c75c5a50fe 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -25,7 +25,7 @@ let Bitwise = ZkProgram({ not: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 16); + return Gadgets.not(a, 255); }, }, and: { From 9ac67bf2d80a07301467a45f3e9a22b9856a08d2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 11:43:45 +0100 Subject: [PATCH 0505/1215] write sumchain gadget --- src/lib/gadgets/foreign-field.ts | 35 ++++++++++++++++++++++++-------- src/lib/gates.ts | 11 ++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 944c1e04c3..f71f6632be 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -1,20 +1,37 @@ import { Field } from '../field.js'; -import { foreignFieldAdd } from '../gates.js'; +import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; import { assert, exists } from './common.js'; -import { L, lMask, twoL, twoLMask } from './range-check.js'; +import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; + +export { ForeignField }; type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; type Sign = -1n | 1n; -function sumchain(xs: Field3[], signs: Sign[], f: bigint) { - assert(xs.length === signs.length + 1, 'inputs and operators match'); +const ForeignField = { sumChain }; + +/** + * computes x[0] + sign[0] * x[1] + ... + sign[n-1] * x[n] modulo f + * + * assumes that inputs are range checked, does range check on the result. + */ +function sumChain(x: Field3[], sign: Sign[], f: bigint) { + assert(x.length === sign.length + 1, 'inputs and operators match'); + + let result = x[0]; + for (let i = 0; i < sign.length; i++) { + ({ result } = singleAdd(result, x[i + 1], sign[i], f)); + } + // final zero row to hold result + Gates.zero(...result); - // TODO + // range check result + multiRangeCheck(...result); } -function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint): Field3 { +function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { let f_ = split(f); let [r0, r1, r2, overflow, carry] = exists(5, () => { @@ -26,8 +43,10 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint): Field3 { let overflow = 0n; if (sign === 1n && r > f) overflow = 1n; if (sign === -1n && r < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; // special case where overflow doesn't change anything - // do the carry + // do the add with carry + // note: this "just works" with negative r01 let r01 = collapse2(x_) + sign * collapse2(y_) - overflow * collapse2(f_); let carry = r01 >> twoL; r01 &= twoLMask; @@ -46,7 +65,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint): Field3 { sign, }); - return [r0, r1, r2]; + return { result: [r0, r1, r2] satisfies Field3, overflow }; } function Field3(x: bigint3): Field3 { diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 36f2a11ced..d0be6abf17 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -4,6 +4,17 @@ import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; export { + Gates, + rangeCheck0, + rangeCheck1, + xor, + zero, + rotate, + generic, + foreignFieldAdd, +}; + +const Gates = { rangeCheck0, rangeCheck1, xor, From d316ab21e70de93f2294f870ae95b92fab6b7395 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 11:50:31 +0100 Subject: [PATCH 0506/1215] add another comment --- src/lib/gadgets/foreign-field.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index f71f6632be..4c0e9eff9c 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -31,6 +31,14 @@ function sumChain(x: Field3[], sign: Sign[], f: bigint) { multiRangeCheck(...result); } +/** + * core building block for non-native addition + * + * **warning**: this just adds the `foreignFieldAdd` row; + * it _must_ be chained with a second row that holds the result in its first 3 cells. + * + * the second row could, for example, be `zero`, `foreignFieldMul`, or another `foreignFieldAdd`. + */ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { let f_ = split(f); @@ -56,14 +64,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return [r0, r1, r2, overflow, carry]; }); - foreignFieldAdd({ - left: x, - right: y, - overflow, - carry, - modulus: f_, - sign, - }); + foreignFieldAdd({ left: x, right: y, overflow, carry, modulus: f_, sign }); return { result: [r0, r1, r2] satisfies Field3, overflow }; } From c277e35eb731e379b31a21ee4549aae543e6ee0a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 11:52:12 +0100 Subject: [PATCH 0507/1215] simplify gates imports --- src/lib/gadgets/bitwise.ts | 2 +- src/lib/gadgets/range-check.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 0eeffc9b52..c2c659dbb5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,7 +1,7 @@ import { Provable } from '../provable.js'; import { Field as Fp } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; -import * as Gates from '../gates.js'; +import { Gates } from '../gates.js'; import { MAX_BITS, assert, diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index f640cd56a7..756388d24d 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,5 +1,5 @@ import { Field } from '../field.js'; -import * as Gates from '../gates.js'; +import { Gates } from '../gates.js'; import { bitSlice, exists } from './common.js'; export { From 8529dcfeb797403316df4b416e099e4bf2eefa5e Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 11:56:15 +0100 Subject: [PATCH 0508/1215] add and sub --- src/lib/gadgets/foreign-field.ts | 10 +++++++++- src/lib/gadgets/gadgets.ts | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 4c0e9eff9c..9f62bd2b13 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -10,7 +10,15 @@ type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; type Sign = -1n | 1n; -const ForeignField = { sumChain }; +const ForeignField = { + add(x: Field3, y: Field3, f: bigint) { + return sumChain([x, y], [1n], f); + }, + sub(x: Field3, y: Field3, f: bigint) { + return sumChain([x, y], [-1n], f); + }, + sumChain, +}; /** * computes x[0] + sign[0] * x[1] + ... + sign[n-1] * x[n] modulo f diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 32ad29acb7..b8ce5dc359 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -8,6 +8,7 @@ import { } from './range-check.js'; import { rotate, xor, and } from './bitwise.js'; import { Field } from '../core.js'; +import { ForeignField } from './foreign-field.js'; export { Gadgets }; @@ -175,4 +176,9 @@ const Gadgets = { compactMultiRangeCheck(xy: Field, z: Field) { return compactMultiRangeCheck(xy, z); }, + + /** + * Gadgets for foreign field operations. + */ + ForeignField, }; From cc7d4014c35149d26ffee37ede993a58249907fa Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 13:41:01 +0100 Subject: [PATCH 0509/1215] add helpers, return result 0.O --- src/lib/gadgets/foreign-field.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 9f62bd2b13..266c35533c 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -4,13 +4,14 @@ import { Tuple } from '../util/types.js'; import { assert, exists } from './common.js'; import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; -export { ForeignField }; +export { ForeignField, Field3 }; type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; type Sign = -1n | 1n; const ForeignField = { + // arithmetic add(x: Field3, y: Field3, f: bigint) { return sumChain([x, y], [1n], f); }, @@ -18,6 +19,14 @@ const ForeignField = { return sumChain([x, y], [-1n], f); }, sumChain, + + // helper methods + from(x: bigint): Field3 { + return Field3(split(x)); + }, + toBigint(x: Field3): bigint { + return collapse(bigint3(x)); + }, }; /** @@ -37,6 +46,8 @@ function sumChain(x: Field3[], sign: Sign[], f: bigint) { // range check result multiRangeCheck(...result); + + return result; } /** From 98666aba3cbf2e7ffa5cae1631bfe3a20b32e019 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 13:49:32 +0100 Subject: [PATCH 0510/1215] sophisticated random generator for any field --- src/lib/testing/random.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 1cae85806e..0d9c457cf2 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -39,6 +39,7 @@ import { ProvableExtended } from '../../bindings/lib/provable-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; +import type { FiniteField } from '../../bindings/crypto/finite_field.js'; export { Random, sample, withHardCoded }; @@ -307,6 +308,7 @@ const Random = Object.assign(Random_, { reject, dice: Object.assign(dice, { ofSize: diceOfSize() }), field, + otherField: fieldWithInvalid, bool, uint32, uint64, @@ -843,12 +845,12 @@ function bignatWithInvalid(max: bigint): RandomWithInvalid { return Object.assign(valid, { invalid }); } -function fieldWithInvalid( - F: typeof Field | typeof Scalar -): RandomWithInvalid { +function fieldWithInvalid(F: FiniteField): RandomWithInvalid { let randomField = Random_(F.random); - let specialField = oneOf(0n, 1n, F(-1)); - let field = oneOf(randomField, randomField, uint64, specialField); + let specialField = oneOf(0n, 1n, F.negate(1n)); + let roughLogSize = 1 << Math.ceil(Math.log2(F.sizeInBits) - 1); + let uint = biguint(roughLogSize); + let field = oneOf(randomField, randomField, uint, specialField); let tooLarge = map(field, (x) => x + F.modulus); let negative = map(field, (x) => -x - 1n); let invalid = oneOf(tooLarge, negative); From 150844048631b5f77f053389eda3cf7bd95c610a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 13:56:49 +0100 Subject: [PATCH 0511/1215] handle constant case --- src/lib/gadgets/foreign-field.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 266c35533c..961206708d 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -1,3 +1,4 @@ +import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; @@ -37,6 +38,14 @@ const ForeignField = { function sumChain(x: Field3[], sign: Sign[], f: bigint) { assert(x.length === sign.length + 1, 'inputs and operators match'); + // constant case + if (x.every((x) => x.every((x) => x.isConstant()))) { + let xBig = x.map(ForeignField.toBigint); + let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); + return ForeignField.from(mod(sum, f)); + } + + // provable case - create chain of ffadd rows let result = x[0]; for (let i = 0; i < sign.length; i++) { ({ result } = singleAdd(result, x[i + 1], sign[i], f)); From 1ef716551dcdbd0c704c72411075e05be3cbbdb5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 13:57:43 +0100 Subject: [PATCH 0512/1215] add unit test for add and sub --- src/lib/gadgets/foreign-field.unit-test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/lib/gadgets/foreign-field.unit-test.ts diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts new file mode 100644 index 0000000000..7599a902a6 --- /dev/null +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -0,0 +1,20 @@ +import type { FiniteField } from '../../bindings/crypto/finite_field.js'; +import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; +import { Spec, equivalentProvable } from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { ForeignField, Field3 } from './foreign-field.js'; + +function foreignField(F: FiniteField): Spec { + let rng = Random.otherField(F); + return { rng, there: ForeignField.from, back: ForeignField.toBigint }; +} + +let { small, babybear, f25519, bls12_381_fq, Fq, Fp } = exampleFields; + +for (let F of [small, babybear, f25519, bls12_381_fq, Fq, Fp]) { + let f = foreignField(F); + let eq2 = equivalentProvable({ from: [f, f], to: f }); + + eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus)); + eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus)); +} From b5cb55cf47bf1f1c7a0dde7696f177cc2eb12fb9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 14:11:49 +0100 Subject: [PATCH 0513/1215] more example fields --- src/lib/gadgets/foreign-field.unit-test.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 7599a902a6..5906a011fe 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -9,9 +9,18 @@ function foreignField(F: FiniteField): Spec { return { rng, there: ForeignField.from, back: ForeignField.toBigint }; } -let { small, babybear, f25519, bls12_381_fq, Fq, Fp } = exampleFields; +let fields = [ + exampleFields.small, + exampleFields.babybear, + exampleFields.f25519, + exampleFields.secp256k1, + exampleFields.secq256k1, + exampleFields.bls12_381_scalar, + exampleFields.Fq, + exampleFields.Fp, +]; -for (let F of [small, babybear, f25519, bls12_381_fq, Fq, Fp]) { +for (let F of fields) { let f = foreignField(F); let eq2 = equivalentProvable({ from: [f, f], to: f }); From 4def5e57a72bcf07e3ebdffea5e14921516b6f11 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 15:51:25 +0100 Subject: [PATCH 0514/1215] add a few specs --- src/lib/testing/equivalent.ts | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 22c1954a91..a437dc85e9 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -15,9 +15,18 @@ export { handleErrors, deepEqual as defaultAssertEqual, id, +}; +export { + field, fieldWithRng, + bigintField, + bool, + boolean, + unit, + array, + record, + fromRandom, }; -export { field, bigintField, bool, boolean, unit }; export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; // a `Spec` tells us how to compare two functions @@ -235,16 +244,51 @@ let bool: ProvableSpec = { back: (x) => x.toBoolean(), provable: Bool, }; -let boolean: Spec = { - rng: Random.boolean, - there: id, - back: id, -}; +let boolean: Spec = fromRandom(Random.boolean); function fieldWithRng(rng: Random): Spec { return { ...field, rng }; } +// spec combinators + +function array( + spec: Spec, + n: Random | number +): Spec { + return { + rng: Random.array(spec.rng, n), + there: (x) => x.map(spec.there), + back: (x) => x.map(spec.back), + }; +} + +function record }>( + specs: Specs +): Spec< + { [k in keyof Specs]: Result1 }, + { [k in keyof Specs]: Result2 } +> { + return { + rng: Random.record(mapObject(specs, (spec) => spec.rng)) as any, + there: (x) => mapObject(specs, (spec, k) => spec.there(x[k])) as any, + back: (x) => mapObject(specs, (spec, k) => spec.back(x[k])) as any, + }; +} + +function mapObject( + t: { [k in K]: T }, + map: (t: T, k: K) => S +): { [k in K]: S } { + return Object.fromEntries( + Object.entries(t).map(([k, v]) => [k, map(v, k as K)]) + ) as any; +} + +function fromRandom(rng: Random): Spec { + return { rng, there: id, back: id }; +} + // helper to ensure two functions throw equivalent errors function handleErrors( From 13c0e559f33df777ab23dfaab5967b790e3dd7b8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 15:51:33 +0100 Subject: [PATCH 0515/1215] expose sign --- src/lib/gadgets/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 961206708d..7802750300 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,7 +5,7 @@ import { Tuple } from '../util/types.js'; import { assert, exists } from './common.js'; import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; -export { ForeignField, Field3 }; +export { ForeignField, Field3, Sign }; type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; From b9c1a00405cf8e663ea969d725228c472a740bd0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 15:51:51 +0100 Subject: [PATCH 0516/1215] add sumchain tests and with zkprogram --- src/lib/gadgets/foreign-field.unit-test.ts | 97 +++++++++++++++++++++- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 5906a011fe..c2eb4307e9 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -1,13 +1,27 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; -import { Spec, equivalentProvable } from '../testing/equivalent.js'; +import { + Spec, + array, + equivalentAsync, + equivalentProvable, + fromRandom, + record, +} from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; -import { ForeignField, Field3 } from './foreign-field.js'; +import { ForeignField, Field3, Sign } from './foreign-field.js'; +import { ZkProgram } from '../proof_system.js'; +import { Provable } from '../provable.js'; +import { Field } from '../field.js'; +import { ProvableExtended, provable, provablePure } from '../circuit_value.js'; +import { TupleN } from '../util/types.js'; +import { assert, exists } from './common.js'; function foreignField(F: FiniteField): Spec { let rng = Random.otherField(F); return { rng, there: ForeignField.from, back: ForeignField.toBigint }; } +let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); let fields = [ exampleFields.small, @@ -20,10 +34,85 @@ let fields = [ exampleFields.Fp, ]; +// tests for witness generation + for (let F of fields) { let f = foreignField(F); let eq2 = equivalentProvable({ from: [f, f], to: f }); - eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus)); - eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus)); + eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); + eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); + + // sumchain of 5 + equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( + (xs, signs) => sumchain(xs, signs, F), + (xs, signs) => ForeignField.sumChain(xs, signs, F.modulus) + ); + + // sumchain up to 100 + let operands = array(record({ x: f, sign }), Random.nat(100)); + + equivalentProvable({ from: [f, operands], to: f })( + (x0, ts) => { + let xs = [x0, ...ts.map((t) => t.x)]; + let signs = ts.map((t) => t.sign); + return sumchain(xs, signs, F); + }, + (x0, ts) => { + let xs = [x0, ...ts.map((t) => t.x)]; + let signs = ts.map((t) => t.sign); + return ForeignField.sumChain(xs, signs, F.modulus); + }, + 'sumchain' + ); +} + +// tests with proving + +let F = exampleFields.secp256k1; +let f = foreignField(F); + +const Field3_ = provablePure([Field, Field, Field] as TupleN); +const Sign = provable(BigInt) as ProvableExtended; + +let ffProgram = ZkProgram({ + name: 'foreign-field', + publicOutput: Field3_, + methods: { + // sumchain of length 5 + sumchain: { + privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], + method(xs, signs) { + return ForeignField.sumChain(xs, signs, F.modulus); + }, + }, + }, +}); + +await ffProgram.compile(); + +let [{ gates }] = ffProgram.analyzeMethods(); + +console.log(gates); + +await equivalentAsync( + { from: [array(f, 5), array(sign, 4)], to: f }, + { runs: 5 } +)( + (xs, signs) => sumchain(xs, signs, F), + async (xs, signs) => { + let proof = await ffProgram.sumchain(xs, signs); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + } +); + +// helper + +function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { + let sum = xs[0]; + for (let i = 0; i < signs.length; i++) { + sum = signs[i] === 1n ? F.add(sum, xs[i + 1]) : F.sub(sum, xs[i + 1]); + } + return sum; } From 7e93bf170e909c27249a425d8ad1496c381575ab Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 16:12:38 +0100 Subject: [PATCH 0517/1215] dang --- src/lib/gadgets/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 7802750300..e85639c928 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -77,7 +77,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { // figure out if there's overflow let r = collapse(x_) + sign * collapse(y_); let overflow = 0n; - if (sign === 1n && r > f) overflow = 1n; + if (sign === 1n && r >= f) overflow = 1n; if (sign === -1n && r < 0n) overflow = -1n; if (f === 0n) overflow = 0n; // special case where overflow doesn't change anything From 6adf304fcc1bef0623f1747611144e4ebc61fdb7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 16:40:02 +0100 Subject: [PATCH 0518/1215] automatically reduce inputs to exists for easier use --- src/lib/gadgets/common.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 6b97c6016b..5c05c40b4f 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -3,6 +3,7 @@ import { Field, FieldConst } from '../field.js'; import { TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; +import { mod } from '../../bindings/crypto/finite_field.js'; const MAX_BITS = 64 as const; @@ -21,7 +22,7 @@ function exists TupleN>( compute: C ) { let varsMl = Snarky.exists(n, () => - MlArray.mapTo(compute(), FieldConst.fromBigint) + MlArray.mapTo(compute(), (x) => FieldConst.fromBigint(mod(x, Field.ORDER))) ); let vars = MlArray.mapFrom(varsMl, (v) => new Field(v)); return TupleN.fromArray(n, vars); From ac773391b042c48945fb7627b8c5d09ad42ff5b6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 17:16:12 +0100 Subject: [PATCH 0519/1215] add proof works --- src/lib/gadgets/foreign-field.ts | 4 +- src/lib/gadgets/foreign-field.unit-test.ts | 59 +++++++++++++++------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index e85639c928..e2dfd5cd18 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -108,12 +108,12 @@ function collapse([x0, x1, x2]: bigint3) { return x0 + (x1 << L) + (x2 << twoL); } function split(x: bigint): bigint3 { - return [x & lMask, (x >> L) & lMask, x >> twoL]; + return [x & lMask, (x >> L) & lMask, (x >> twoL) & lMask]; } function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { return x0 + (x1 << L); } function split2(x: bigint): [bigint, bigint] { - return [x & lMask, x >> L]; + return [x & lMask, (x >> L) & lMask]; } diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index c2eb4307e9..473dc3f26f 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -15,11 +15,19 @@ import { Provable } from '../provable.js'; import { Field } from '../field.js'; import { ProvableExtended, provable, provablePure } from '../circuit_value.js'; import { TupleN } from '../util/types.js'; -import { assert, exists } from './common.js'; +import { assert } from './common.js'; + +const Field3_ = provablePure([Field, Field, Field] as TupleN); +const Sign = provable(BigInt) as ProvableExtended; function foreignField(F: FiniteField): Spec { let rng = Random.otherField(F); - return { rng, there: ForeignField.from, back: ForeignField.toBigint }; + return { + rng, + there: ForeignField.from, + back: ForeignField.toBigint, + provable: Field3_, + }; } let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); @@ -72,20 +80,24 @@ for (let F of fields) { let F = exampleFields.secp256k1; let f = foreignField(F); -const Field3_ = provablePure([Field, Field, Field] as TupleN); -const Sign = provable(BigInt) as ProvableExtended; - let ffProgram = ZkProgram({ name: 'foreign-field', publicOutput: Field3_, methods: { - // sumchain of length 5 - sumchain: { - privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], - method(xs, signs) { - return ForeignField.sumChain(xs, signs, F.modulus); + add: { + privateInputs: [Field3_, Field3_], + method(x, y) { + return ForeignField.add(x, y, F.modulus); }, }, + + // // sumchain of length 5 + // sumchain: { + // privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], + // method(xs, signs) { + // return ForeignField.sumChain(xs, signs, F.modulus); + // }, + // }, }, }); @@ -95,18 +107,29 @@ let [{ gates }] = ffProgram.analyzeMethods(); console.log(gates); -await equivalentAsync( - { from: [array(f, 5), array(sign, 4)], to: f }, - { runs: 5 } -)( - (xs, signs) => sumchain(xs, signs, F), - async (xs, signs) => { - let proof = await ffProgram.sumchain(xs, signs); +await equivalentAsync({ from: [f, f], to: f }, { runs: 5 })( + F.add, + async (x, y) => { + let proof = await ffProgram.add(x, y); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; - } + }, + 'prove add' ); +// await equivalentAsync( +// { from: [array(f, 5), array(sign, 4)], to: f }, +// { runs: 5 } +// )( +// (xs, signs) => sumchain(xs, signs, F), +// async (xs, signs) => { +// let proof = await ffProgram.sumchain(xs, signs); +// assert(await ffProgram.verify(proof), 'verifies'); +// return proof.publicOutput; +// }, +// 'prove chain' +// ); + // helper function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { From 1b135aa1f0d8d7531223c5c6ec613250517982dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 17:28:19 +0100 Subject: [PATCH 0520/1215] actually it's easier this way --- src/lib/field.ts | 4 ++-- src/lib/gadgets/common.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 5d810ef20f..e6836237a9 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -28,7 +28,7 @@ function constToBigint(x: FieldConst): Fp { return x[1]; } function constFromBigint(x: Fp): FieldConst { - return [0, x]; + return [0, Fp(x)]; } const FieldConst = { @@ -39,7 +39,7 @@ const FieldConst = { }, [0]: constFromBigint(0n), [1]: constFromBigint(1n), - [-1]: constFromBigint(Fp(-1n)), + [-1]: constFromBigint(-1n), }; enum FieldType { diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 5c05c40b4f..6b97c6016b 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -3,7 +3,6 @@ import { Field, FieldConst } from '../field.js'; import { TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; -import { mod } from '../../bindings/crypto/finite_field.js'; const MAX_BITS = 64 as const; @@ -22,7 +21,7 @@ function exists TupleN>( compute: C ) { let varsMl = Snarky.exists(n, () => - MlArray.mapTo(compute(), (x) => FieldConst.fromBigint(mod(x, Field.ORDER))) + MlArray.mapTo(compute(), FieldConst.fromBigint) ); let vars = MlArray.mapFrom(varsMl, (v) => new Field(v)); return TupleN.fromArray(n, vars); From 68ff7dafc6ce1b549b635d794b9b6ad70bab537a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 17:28:36 +0100 Subject: [PATCH 0521/1215] sub proving works --- src/lib/gadgets/foreign-field.unit-test.ts | 57 ++++++++++++++-------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 473dc3f26f..e84d9992d7 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -91,13 +91,20 @@ let ffProgram = ZkProgram({ }, }, - // // sumchain of length 5 - // sumchain: { - // privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], - // method(xs, signs) { - // return ForeignField.sumChain(xs, signs, F.modulus); - // }, - // }, + sub: { + privateInputs: [Field3_, Field3_], + method(x, y) { + return ForeignField.sub(x, y, F.modulus); + }, + }, + + // sumchain of length 5 + sumchain: { + privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], + method(xs, signs) { + return ForeignField.sumChain(xs, signs, F.modulus); + }, + }, }, }); @@ -107,7 +114,7 @@ let [{ gates }] = ffProgram.analyzeMethods(); console.log(gates); -await equivalentAsync({ from: [f, f], to: f }, { runs: 5 })( +await equivalentAsync({ from: [f, f], to: f }, { runs: 0 })( F.add, async (x, y) => { let proof = await ffProgram.add(x, y); @@ -117,18 +124,28 @@ await equivalentAsync({ from: [f, f], to: f }, { runs: 5 })( 'prove add' ); -// await equivalentAsync( -// { from: [array(f, 5), array(sign, 4)], to: f }, -// { runs: 5 } -// )( -// (xs, signs) => sumchain(xs, signs, F), -// async (xs, signs) => { -// let proof = await ffProgram.sumchain(xs, signs); -// assert(await ffProgram.verify(proof), 'verifies'); -// return proof.publicOutput; -// }, -// 'prove chain' -// ); +await equivalentAsync({ from: [f, f], to: f }, { runs: 10 })( + F.sub, + async (x, y) => { + let proof = await ffProgram.sub(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove sub' +); + +await equivalentAsync( + { from: [array(f, 5), array(sign, 4)], to: f }, + { runs: 0 } +)( + (xs, signs) => sumchain(xs, signs, F), + async (xs, signs) => { + let proof = await ffProgram.sumchain(xs, signs); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove chain' +); // helper From d4db523460eaf263f444f345bb859319d01d4ff2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 17:37:51 +0100 Subject: [PATCH 0522/1215] sumchain works (signs are constants, d'oh) --- src/lib/gadgets/foreign-field.unit-test.ts | 57 ++++------------------ 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index e84d9992d7..0c173c8352 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -80,28 +80,16 @@ for (let F of fields) { let F = exampleFields.secp256k1; let f = foreignField(F); +let chainLength = 5; +let signs = [-1n, 1n, -1n, -1n] satisfies Sign[]; + let ffProgram = ZkProgram({ name: 'foreign-field', publicOutput: Field3_, methods: { - add: { - privateInputs: [Field3_, Field3_], - method(x, y) { - return ForeignField.add(x, y, F.modulus); - }, - }, - - sub: { - privateInputs: [Field3_, Field3_], - method(x, y) { - return ForeignField.sub(x, y, F.modulus); - }, - }, - - // sumchain of length 5 sumchain: { - privateInputs: [Provable.Array(Field3_, 5), Provable.Array(Sign, 4)], - method(xs, signs) { + privateInputs: [Provable.Array(Field3_, chainLength)], + method(xs) { return ForeignField.sumChain(xs, signs, F.modulus); }, }, @@ -110,37 +98,10 @@ let ffProgram = ZkProgram({ await ffProgram.compile(); -let [{ gates }] = ffProgram.analyzeMethods(); - -console.log(gates); - -await equivalentAsync({ from: [f, f], to: f }, { runs: 0 })( - F.add, - async (x, y) => { - let proof = await ffProgram.add(x, y); - assert(await ffProgram.verify(proof), 'verifies'); - return proof.publicOutput; - }, - 'prove add' -); - -await equivalentAsync({ from: [f, f], to: f }, { runs: 10 })( - F.sub, - async (x, y) => { - let proof = await ffProgram.sub(x, y); - assert(await ffProgram.verify(proof), 'verifies'); - return proof.publicOutput; - }, - 'prove sub' -); - -await equivalentAsync( - { from: [array(f, 5), array(sign, 4)], to: f }, - { runs: 0 } -)( - (xs, signs) => sumchain(xs, signs, F), - async (xs, signs) => { - let proof = await ffProgram.sumchain(xs, signs); +await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 5 })( + (xs) => sumchain(xs, signs, F), + async (xs) => { + let proof = await ffProgram.sumchain(xs); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; }, From de3a15c510a5259945bf164e34d0d68a0e941684 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 2 Nov 2023 17:38:05 +0100 Subject: [PATCH 0523/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 0823354ae6..f14ffa28ed 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 0823354ae63477bf65120854e54f594c917f7c8f +Subproject commit f14ffa28ed319880fa0d9bca109ca390958b9361 From 2d49242d82ba28a87bcf13a8fc7c7a076ebf5d5b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Nov 2023 10:18:47 -0700 Subject: [PATCH 0524/1215] chore(bitwise.unit-test.ts): remove comments --- src/lib/gadgets/bitwise.unit-test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 8261c38545..a1ae04371d 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,10 +20,6 @@ let maybeUint64: Spec = { let uint = (length: number) => fieldWithRng(Random.biguint(length)); -// -------------------------- -// Bitwise Gates -// -------------------------- - let Bitwise = ZkProgram({ name: 'bitwise', publicOutput: Field, From e2ceabeba51196a7c298226824e6aafc82cc8844 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Nov 2023 10:59:17 -0700 Subject: [PATCH 0525/1215] chore(bindings): update commit --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index af22d5bca5..03e8047966 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit af22d5bca5cc1080645f7887f684bac646d1ca1f +Subproject commit 03e8047966da86073d0dc3a06067f5568c8e873c From e83153adfdfa9a8ad30407bb017e546c735babd0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Nov 2023 11:21:53 -0700 Subject: [PATCH 0526/1215] docs(gadgets.ts): update documentation for rotate, leftShift, rightShift --- src/lib/gadgets/gadgets.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 2680ce05ed..112bfa7217 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -46,7 +46,7 @@ const Gadgets = { * * **Important:** The gadget assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the rotation. * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * @@ -117,7 +117,7 @@ const Gadgets = { * * **Important:** The gadgets assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the shift. + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. * Therefore, to safely use `leftShift()`, you need to make sure that the values passed in are range checked to 64 bits. * For example, this can be done with {@link Gadgets.rangeCheck64}. * @@ -150,7 +150,7 @@ const Gadgets = { * * **Important:** The gadgets assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the shift. + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. * To safely use `rightShift()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * From 1eeab64ed7a30c4b49f730d51aaa315658991210 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 12:01:00 -0700 Subject: [PATCH 0527/1215] docs(gadgets.ts): update `not` doc comments to include examples of checked and unchecked versions --- src/lib/gadgets/gadgets.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 13b8bb7f4f..40a853e05f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -124,10 +124,10 @@ const Gadgets = { * **Note:** Specifying a larger `length` parameter adds additional constraints. * * - * NOT is implemented in two different ways. If the 'checked' parameter is set to 'true' + * NOT is implemented in two different ways. If the `checked` parameter is set to `true` * the {@link Gadgets.xor} gadget is reusesd the with a second argument to be an * all one bitmask the same length. This approach needs as many rows as an XOR would need - * for a single negation. If the 'checked' parameter is set to 'false' NOT is + * for a single negation. If the 'checked' parameter is set to 'false', NOT is * implementad as a subtraction of the input from the all one bitmask. This * implementation is returned by default if no 'checked' parameter is provided. * @@ -135,16 +135,24 @@ const Gadgets = { * * @example * ```ts + * // not-ing 4 bits with the unchecked version * let a = Field(0b0101); - * let b = not(a,4,false); // not-ing 4 bits + * let b = gadgets.not(a,4,false); + * + * b.assertEquals(0b1010); + * + * // not-ing 4 bits with the checked version utilizing the xor gadget + * let a = Field(0b0101); + * let b = gadgets.not(a,4,true); * * b.assertEquals(0b1010); * ``` * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. - * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. - * It is set to false by default if no parameter is provided. + * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reusesd. If it is set to `false` + * NOT is implementad as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * */ not(a: Field, length: number, checked: boolean = false) { return not(a, length, checked); @@ -160,9 +168,9 @@ const Gadgets = { * Where:\ * `a + b = sum`\ * `a ^ b = xor`\ - * `a & b = and`You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) - * + * `a & b = and` * + *You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * From d09663afbf07150fe99ac780cc257fcdb5036265 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 12:19:54 -0700 Subject: [PATCH 0528/1215] refactor(bitwise.ts): simplify the not function by using conditional return statements instead of separate variables --- src/lib/gadgets/bitwise.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index fc97425029..d23350efca 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -45,10 +45,11 @@ function not(a: Field, length: number, checked: boolean = false) { allOnesF.assertEquals(allOnes); - let notChecked = xor(a, allOnes, length); - let notUnchecked = allOnes.sub(a); - - return checked ? notChecked : notUnchecked; + if (checked) { + return xor(a, allOnes, length); + } else { + return allOnes.sub(a); + } } function xor(a: Field, b: Field, length: number) { From a2ef8548c39ad0555f60c621577ef8dc3258f01b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 12:43:41 -0700 Subject: [PATCH 0529/1215] chore(regression_test.json): dump vks --- src/examples/regression_test.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 91e4f93a47..6c5ce1bf3c 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -176,7 +176,11 @@ "rows": 15, "digest": "b3595a9cc9562d4f4a3a397b6de44971" }, - "not": { + "notUnchecked": { + "rows": 2, + "digest": "fa18d403c061ef2be221baeae18ca19d" + }, + "notChecked": { "rows": 17, "digest": "5e01b2cad70489c7bec1546b84ac868d" }, From d7d809dbed154a921d481b8b6a34ac4b1e1ec316 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 12:52:42 -0700 Subject: [PATCH 0530/1215] feat(bitwise.unit-test.ts): add `notChecked` and `notUnchecked` tests --- src/examples/primitive_constraint_system.ts | 18 ++++++++++++------ src/lib/gadgets/bitwise.unit-test.ts | 20 ++++++++++++++++---- src/lib/gadgets/gadgets.ts | 4 ++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 7dbb777a37..923727e1ba 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -79,13 +79,19 @@ const BitwiseMock = { Gadgets.xor(a, b, 48); Gadgets.xor(a, b, 64); }, - - not() { + notUnchecked() { + let a = Provable.witness(Field, () => new Field(5n)); + Gadgets.not(a, 16, false); + Gadgets.not(a, 32, false); + Gadgets.not(a, 48, false); + Gadgets.not(a, 64, false); + }, + notChecked() { let a = Provable.witness(Field, () => new Field(5n)); - Gadgets.not(a, 16); - Gadgets.not(a, 32); - Gadgets.not(a, 48); - Gadgets.not(a, 64); + Gadgets.not(a, 16, true); + Gadgets.not(a, 32, true); + Gadgets.not(a, 48, true); + Gadgets.not(a, 64, true); }, and() { let a = Provable.witness(Field, () => new Field(5n)); diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index c75c5a50fe..f4ffa6b74c 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -22,10 +22,16 @@ let Bitwise = ZkProgram({ return Gadgets.xor(a, b, 255); }, }, - not: { + notChecked: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 255); + return Gadgets.not(a, 255, true); + }, + }, + notUnchecked: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.not(a, 255, false); }, }, and: { @@ -56,9 +62,15 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); (x, y) => x & y, (x, y) => Gadgets.and(x, y, length) ); - equivalent({ from: [uint(length), uint(length)], to: field })( + // NOT checked + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.not(x, length), + (x) => Gadgets.not(x, length, true) + ); + // NOT unchecked + equivalent({ from: [uint(length)], to: field })( (x) => Fp.not(x, length), - (x) => Gadgets.not(x, length) + (x) => Gadgets.not(x, length, false) ); }); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 40a853e05f..24bf66a7c9 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -127,9 +127,9 @@ const Gadgets = { * NOT is implemented in two different ways. If the `checked` parameter is set to `true` * the {@link Gadgets.xor} gadget is reusesd the with a second argument to be an * all one bitmask the same length. This approach needs as many rows as an XOR would need - * for a single negation. If the 'checked' parameter is set to 'false', NOT is + * for a single negation. If the `checked` parameter is set to `false`, NOT is * implementad as a subtraction of the input from the all one bitmask. This - * implementation is returned by default if no 'checked' parameter is provided. + * implementation is returned by default if no `checked` parameter is provided. * *You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) * From c57dbcdf4fcc289510f93970f68268197bc03ac3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Nov 2023 13:25:14 -0700 Subject: [PATCH 0531/1215] chore(bindings): update commit --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 03e8047966..e17660291a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 03e8047966da86073d0dc3a06067f5568c8e873c +Subproject commit e17660291a884877639fdbdd66c6537b75855661 From 358e3d88ec2dbe664cb6fa3134df0ff6fa54146b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Nov 2023 13:29:05 -0700 Subject: [PATCH 0532/1215] feat(CHANGELOG.md): add new provable methods Gadgets.leftShift(), Gadgets.rightShift(), and Gadgets.and() to support bitwise operations for native field elements --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2588d44c1f..b64391d853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/e8e7510e1...HEAD) -> No unreleased changes yet +### Added + +- `Gadgets.leftShift() / Gadgets.rightShift()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1194 +- `Gadgets.and()`, new provable method to support bitwise and for native field elements. https://github.com/o1-labs/o1js/pull/1193 ## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) From c3aa4cdee481b98c7906efa97f68bc7de698ccd9 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 13:37:03 -0700 Subject: [PATCH 0533/1215] fix(gadgets.ts): update doc comment --- src/lib/gadgets/bitwise.unit-test.ts | 16 ++++++++-------- src/lib/gadgets/gadgets.ts | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index f4ffa6b74c..e4caaaafaa 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -22,16 +22,16 @@ let Bitwise = ZkProgram({ return Gadgets.xor(a, b, 255); }, }, - notChecked: { + notUnchecked: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 255, true); + return Gadgets.not(a, 255, false); }, }, - notUnchecked: { + notChecked: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 255, false); + return Gadgets.not(a, 255, true); }, }, and: { @@ -62,15 +62,15 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); (x, y) => x & y, (x, y) => Gadgets.and(x, y, length) ); - // NOT checked + // NOT unchecked equivalent({ from: [uint(length)], to: field })( (x) => Fp.not(x, length), - (x) => Gadgets.not(x, length, true) + (x) => Gadgets.not(x, length, false) ); - // NOT unchecked + // NOT checked equivalent({ from: [uint(length)], to: field })( (x) => Fp.not(x, length), - (x) => Gadgets.not(x, length, false) + (x) => Gadgets.not(x, length, true) ); }); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 24bf66a7c9..673ecb4351 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -150,8 +150,8 @@ const Gadgets = { * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. - * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reusesd. If it is set to `false` - * NOT is implementad as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reusesd. + * If it is set to `false`, NOT is implementad as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. * */ not(a: Field, length: number, checked: boolean = false) { From 959cac27f70140c444431b4dc2e93933f4f22ca9 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 2 Nov 2023 22:43:04 +0100 Subject: [PATCH 0534/1215] ffmul gate wrapper --- src/lib/gates.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index d0be6abf17..e18ad6e2d5 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -12,6 +12,7 @@ export { rotate, generic, foreignFieldAdd, + foreignFieldMul, }; const Gates = { @@ -184,3 +185,48 @@ function foreignFieldAdd({ FieldConst.fromBigint(sign) ); } + +/** + * Foreign field multiplication + */ +function foreignFieldMul(inputs: { + left: TupleN; + right: TupleN; + remainder: TupleN; + quotient: TupleN; + quotientHiBound: Field; + product1: TupleN; + carry0: Field; + carry1p: TupleN; + carry1c: TupleN; + foreignFieldModulus2: bigint; + negForeignFieldModulus: TupleN; +}) { + let { + left, + right, + remainder, + quotient, + quotientHiBound, + product1, + carry0, + carry1p, + carry1c, + foreignFieldModulus2, + negForeignFieldModulus, + } = inputs; + + Snarky.gates.foreignFieldMul( + MlTuple.mapTo(left, (x) => x.value), + MlTuple.mapTo(right, (x) => x.value), + MlTuple.mapTo(remainder, (x) => x.value), + MlTuple.mapTo(quotient, (x) => x.value), + quotientHiBound.value, + MlTuple.mapTo(product1, (x) => x.value), + carry0.value, + MlTuple.mapTo(carry1p, (x) => x.value), + MlTuple.mapTo(carry1c, (x) => x.value), + FieldConst.fromBigint(foreignFieldModulus2), + MlTuple.mapTo(negForeignFieldModulus, FieldConst.fromBigint) + ); +} From 1dbee98fe19b170fbc273f7395906fa9d65e2f5b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 14:50:19 -0700 Subject: [PATCH 0535/1215] feat(gadgets.ts): correct typos in doc comment --- src/lib/gadgets/gadgets.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 673ecb4351..7335a2afa8 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -113,7 +113,7 @@ const Gadgets = { * Web/JavaScript/Reference/Operators/Bitwise_NOT). * * **Note:** The NOT gate only operates over the amount - * of bits specified by the 'length' paramenter. + * of bits specified by the 'length' parameter. * * A NOT gate works by returning `1` in each bit position if the * corresponding bit of the operand is `0`, and returning `0` if the @@ -125,33 +125,33 @@ const Gadgets = { * * * NOT is implemented in two different ways. If the `checked` parameter is set to `true` - * the {@link Gadgets.xor} gadget is reusesd the with a second argument to be an + * the {@link Gadgets.xor} gadget is reused with a second argument to be an * all one bitmask the same length. This approach needs as many rows as an XOR would need * for a single negation. If the `checked` parameter is set to `false`, NOT is - * implementad as a subtraction of the input from the all one bitmask. This + * implemented as a subtraction of the input from the all one bitmask. This * implementation is returned by default if no `checked` parameter is provided. * - *You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) * * @example * ```ts * // not-ing 4 bits with the unchecked version * let a = Field(0b0101); - * let b = gadgets.not(a,4,false); + * let b = Gadgets.not(a,4,false); * * b.assertEquals(0b1010); * * // not-ing 4 bits with the checked version utilizing the xor gadget * let a = Field(0b0101); - * let b = gadgets.not(a,4,true); + * let b = Gadgets.not(a,4,true); * * b.assertEquals(0b1010); * ``` * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. - * @param checked - Optional Boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reusesd. - * If it is set to `false`, NOT is implementad as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reused. + * If it is set to `false`, NOT is implemented as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. * */ not(a: Field, length: number, checked: boolean = false) { @@ -170,7 +170,7 @@ const Gadgets = { * `a ^ b = xor`\ * `a & b = and` * - *You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) * * The `length` parameter lets you define how many bits should be compared. `length` is rounded to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well. * From adf5b1206ca3cde419fb8fa451e2a956f0263312 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 15:20:58 -0700 Subject: [PATCH 0536/1215] fix(bitwise.ts): change the condition in the assert statement to check if the length is less than the maximum field size in bits instead of less than or equal to, to ensure it doesn't exceed the maximum size --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index d23350efca..367d330170 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -19,7 +19,7 @@ function not(a: Field, length: number, checked: boolean = false) { // Check that length does not exceed maximum field size in bits assert( - length <= Field.sizeInBits(), + length < Field.sizeInBits(), `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` ); From 696bc5c7933f8d81c3d5a5cbd429b2a5174f5bf8 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 15:38:29 -0700 Subject: [PATCH 0537/1215] chore(bindings): update subproject commit hash to 22af158a9bd66b1b74376b1a36517722e931fcf6 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index cfb9bb78ec..22af158a9b 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit cfb9bb78ec4a4f229cdd7d10ffd659afdbe7bc7f +Subproject commit 22af158a9bd66b1b74376b1a36517722e931fcf6 From 29fc6319be06207d9797a2e017607d1b2e9a579d Mon Sep 17 00:00:00 2001 From: ymekuria Date: Thu, 2 Nov 2023 15:41:17 -0700 Subject: [PATCH 0538/1215] Merge branch main into feature/NOT-gadget --- CHANGELOG.md | 5 +- src/examples/primitive_constraint_system.ts | 10 ++ src/examples/regression_test.json | 8 ++ src/lib/gadgets/bitwise.ts | 36 ++++++- src/lib/gadgets/bitwise.unit-test.ts | 102 +++++++++++--------- src/lib/gadgets/gadgets.ts | 73 +++++++++++++- 6 files changed, 183 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2588d44c1f..b64391d853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/e8e7510e1...HEAD) -> No unreleased changes yet +### Added + +- `Gadgets.leftShift() / Gadgets.rightShift()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1194 +- `Gadgets.and()`, new provable method to support bitwise and for native field elements. https://github.com/o1-labs/o1js/pull/1193 ## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) diff --git a/src/examples/primitive_constraint_system.ts b/src/examples/primitive_constraint_system.ts index 923727e1ba..9e47d24333 100644 --- a/src/examples/primitive_constraint_system.ts +++ b/src/examples/primitive_constraint_system.ts @@ -93,6 +93,16 @@ const BitwiseMock = { Gadgets.not(a, 48, true); Gadgets.not(a, 64, true); }, + leftShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.leftShift(a, 2); + Gadgets.leftShift(a, 4); + }, + rightShift() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rightShift(a, 2); + Gadgets.rightShift(a, 4); + }, and() { let a = Provable.witness(Field, () => new Field(5n)); let b = Provable.witness(Field, () => new Field(5n)); diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index 6c5ce1bf3c..b702555394 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -184,6 +184,14 @@ "rows": 17, "digest": "5e01b2cad70489c7bec1546b84ac868d" }, + "leftShift": { + "rows": 7, + "digest": "66de39ad3dd5807f760341ec85a6cc41" + }, + "rightShift": { + "rows": 7, + "digest": "a32264f2d4c3092f30d600fa9506385b" + }, "and": { "rows": 19, "digest": "647e6fd1852873d1c326ba1cd269cff2" diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 367d330170..0943eec543 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -11,7 +11,7 @@ import { } from './common.js'; import { rangeCheck64 } from './range-check.js'; -export { xor, not, and, rotate }; +export { xor, not, rotate, and, rightShift, leftShift }; function not(a: Field, length: number, checked: boolean = false) { // check that input length is positive @@ -284,3 +284,37 @@ function rot( rangeCheck64(excess); return [rotated, excess, shifted]; } + +function rightShift(field: Field, bits: number) { + assert( + bits >= 0 && bits <= MAX_BITS, + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); + + if (field.isConstant()) { + assert( + field.toBigInt() < 2n ** BigInt(MAX_BITS), + `rightShift: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.rightShift(field.toBigInt(), bits)); + } + const [, excess] = rot(field, bits, 'right'); + return excess; +} + +function leftShift(field: Field, bits: number) { + assert( + bits >= 0 && bits <= MAX_BITS, + `rightShift: expected bits to be between 0 and 64, got ${bits}` + ); + + if (field.isConstant()) { + assert( + field.toBigInt() < 2n ** BigInt(MAX_BITS), + `rightShift: expected field to be at most 64 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.leftShift(field.toBigInt(), bits)); + } + const [, , shifted] = rot(field, bits, 'left'); + return shifted; +} diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index e4caaaafaa..24e348e4a8 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -9,8 +9,16 @@ import { import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; -import { test, Random } from '../testing/property.js'; -import { Provable } from '../provable.js'; +import { Random } from '../testing/property.js'; + +let maybeUint64: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + +let uint = (length: number) => fieldWithRng(Random.biguint(length)); let Bitwise = ZkProgram({ name: 'bitwise', @@ -46,13 +54,23 @@ let Bitwise = ZkProgram({ return Gadgets.rotate(a, 12, 'left'); }, }, + leftShift: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.leftShift(a, 12); + }, + }, + rightShift: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.rightShift(a, 12); + }, + }, }, }); await Bitwise.compile(); -let uint = (length: number) => fieldWithRng(Random.biguint(length)); - [2, 4, 8, 16, 32, 64, 128].forEach((length) => { equivalent({ from: [uint(length), uint(length)], to: field })( (x, y) => x ^ y, @@ -74,27 +92,20 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length)); ); }); -test( - Random.uint64, - Random.nat(64), - Random.boolean, - (x, n, direction, assert) => { - let z = Field(x); - let r1 = Fp.rot(x, n, direction ? 'left' : 'right'); - Provable.runAndCheck(() => { - let f = Provable.witness(Field, () => z); - let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right'); - Provable.asProver(() => assert(r1 === r2.toBigInt())); - }); - } -); - -let maybeUint64: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => - mod(x, Field.ORDER) - ), -}; +[2, 4, 8, 16, 32, 64].forEach((length) => { + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rot(x, 12, 'left'), + (x) => Gadgets.rotate(x, 12, 'left') + ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.leftShift(x, 12), + (x) => Gadgets.leftShift(x, 12) + ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rightShift(x, 12), + (x) => Gadgets.rightShift(x, 12) + ); +}); await equivalentAsync( { from: [maybeUint64, maybeUint64], to: field }, @@ -137,25 +148,24 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); -function testRot( - field: Field, - bits: number, - mode: 'left' | 'right', - result: Field -) { - Provable.runAndCheck(() => { - let output = Gadgets.rotate(field, bits, mode); - output.assertEquals(result, `rot(${field}, ${bits}, ${mode})`); - }); -} +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.leftShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.leftShift(x); + return proof.publicOutput; + } +); -testRot(Field(0), 0, 'left', Field(0)); -testRot(Field(0), 32, 'right', Field(0)); -testRot(Field(1), 1, 'left', Field(2)); -testRot(Field(1), 63, 'left', Field(9223372036854775808n)); -testRot(Field(256), 4, 'right', Field(16)); -testRot(Field(1234567890), 32, 'right', Field(5302428712241725440)); -testRot(Field(2651214356120862720), 32, 'right', Field(617283945)); -testRot(Field(1153202983878524928), 32, 'right', Field(268500993)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); -testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n)); +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.rightShift(x, 12); + }, + async (x) => { + let proof = await Bitwise.rightShift(x); + return proof.publicOutput; + } +); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 7335a2afa8..d38596e609 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -2,7 +2,7 @@ * Wrapper file for various gadgets, with a namespace and doccomments. */ import { rangeCheck64 } from './range-check.js'; -import { not, rotate, xor, and } from './bitwise.js'; +import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; export { Gadgets }; @@ -46,7 +46,7 @@ const Gadgets = { * * **Important:** The gadget assumes that its input is at most 64 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and does not prove correct execution of the rotation. + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the rotation. * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * @@ -157,6 +157,72 @@ const Gadgets = { not(a: Field, length: number, checked: boolean = false) { return not(a, length, checked); }, + + /** + * Performs a left shift operation on the provided {@link Field} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * Therefore, to safely use `leftShift()`, you need to make sure that the values passed in are range checked to 64 bits. + * For example, this can be done with {@link Gadgets.rangeCheck64}. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.leftShift(x, 2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + leftShift(field: Field, bits: number) { + return leftShift(field, bits); + }, + + /** + * Performs a right shift operation on the provided {@link Field} element. + * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. + * The `rightShift` function utilizes the rotation method internally to implement this operation. + * + * * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * To safely use `rightShift()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * + * @throws Throws an error if the input value exceeds 64 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.rightShift(x, 2); // right shift by 2 bits + * y.assertEquals(0b000011); // 3 in binary + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits + * ``` + */ + rightShift(field: Field, bits: number) { + return rightShift(field, bits); + }, /** * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. @@ -179,11 +245,12 @@ const Gadgets = { * **Note:** Both {@link Field} elements need to fit into `2^paddedLength - 1`. Otherwise, an error is thrown and no proof can be generated. * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. * + * @example * ```typescript * let a = Field(3); // ... 000011 * let b = Field(5); // ... 000101 * - * let c = and(a, b, 2); // ... 000001 + * let c = Gadgets.and(a, b, 2); // ... 000001 * c.assertEquals(1); * ``` */ From 7dab788ab5926275178b8d2b7955fbfd10d5c608 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 2 Nov 2023 23:51:19 +0100 Subject: [PATCH 0539/1215] ffmul witnesses --- src/lib/gadgets/foreign-field.ts | 78 +++++++++++++++++++++++++++++--- src/lib/gadgets/range-check.ts | 20 +++----- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index e2dfd5cd18..0dfe5966c1 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -2,8 +2,8 @@ import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; -import { assert, exists } from './common.js'; -import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; +import { assert, bitSlice, exists } from './common.js'; +import { L, lMask, multiRangeCheck, L2, l2Mask, L3 } from './range-check.js'; export { ForeignField, Field3, Sign }; @@ -84,8 +84,8 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { // do the add with carry // note: this "just works" with negative r01 let r01 = collapse2(x_) + sign * collapse2(y_) - overflow * collapse2(f_); - let carry = r01 >> twoL; - r01 &= twoLMask; + let carry = r01 >> L2; + r01 &= l2Mask; let [r0, r1] = split2(r01); let r2 = x_[2] + sign * y_[2] - overflow * f_[2] + carry; @@ -97,6 +97,72 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return { result: [r0, r1, r2] satisfies Field3, overflow }; } +function multiply(aF: Field3, bF: Field3, f: bigint) { + // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md + let f_ = (1n << L3) - f; + let [f_0, f_1, f_2] = split(f_); + + let witnesses = exists(27, () => { + // split inputs into 3 limbs + let [a0, a1, a2] = bigint3(aF); + let [b0, b1, b2] = bigint3(bF); + let a = collapse([a0, a1, a2]); + let b = collapse([b0, b1, b2]); + + // compute q and r such that a*b = q*f + r + let ab = a * b; + let q = ab / f; + let r = ab - q * f; + + let [q0, q1, q2] = split(q); + let [r0, r1, r2] = split(r); + let r01 = collapse2([r0, r1]); + + // compute product terms + let p0 = a0 * b0 + q0 * f_0; + let p1 = a0 * b1 + a1 * b0 + q0 * f_1 + q1 * f_0; + let p2 = a0 * b2 + a1 * b1 + a2 * b0 + q0 * f_2 + q1 * f_1 + q2 * f_0; + + let [p10, p110, p111] = split(p1); + let p11 = collapse2([p110, p111]); + + // carry bottom limbs + let c0 = (p0 + (p10 << L) - r01) >> L2; + + // carry top limb + let c1 = (p2 - r2 + p11 + c0) >> L; + + // split high carry + let c1_00 = bitSlice(c1, 0, 12); + let c1_12 = bitSlice(c1, 12, 12); + let c1_24 = bitSlice(c1, 24, 12); + let c1_36 = bitSlice(c1, 36, 12); + let c1_48 = bitSlice(c1, 48, 12); + let c1_60 = bitSlice(c1, 60, 12); + let c1_72 = bitSlice(c1, 72, 12); + let c1_84 = bitSlice(c1, 84, 2); + let c1_86 = bitSlice(c1, 86, 2); + let c1_88 = bitSlice(c1, 88, 2); + let c1_90 = bitSlice(c1, 90, 1); + + // quotient high bound + let q2Bound = q2 + (1n << L) - (f >> L2) - 1n; + + // prettier-ignore + return [ + a0, a1, a2, + b0, b1, b2, + r01, r2, + q0, q1, q2, + q2Bound, + p10, p110, p111, + c0, + c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72, + c1_84, c1_86, c1_88, c1_90, + ]; + }); +} + function Field3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } @@ -105,10 +171,10 @@ function bigint3(x: Field3): bigint3 { } function collapse([x0, x1, x2]: bigint3) { - return x0 + (x1 << L) + (x2 << twoL); + return x0 + (x1 << L) + (x2 << L2); } function split(x: bigint): bigint3 { - return [x & lMask, (x >> L) & lMask, (x >> twoL) & lMask]; + return [x & lMask, (x >> L) & lMask, (x >> L2) & lMask]; } function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 756388d24d..8e57bde4bc 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,15 +2,8 @@ import { Field } from '../field.js'; import { Gates } from '../gates.js'; import { bitSlice, exists } from './common.js'; -export { - rangeCheck64, - multiRangeCheck, - compactMultiRangeCheck, - L, - twoL, - lMask, - twoLMask, -}; +export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; +export { L, L2, L3, lMask, l2Mask }; /** * Asserts that x is in the range [0, 2^64) @@ -59,9 +52,10 @@ function rangeCheck64(x: Field) { // default bigint limb size const L = 88n; -const twoL = 2n * L; +const L2 = 2n * L; +const L3 = 3n * L; const lMask = (1n << L) - 1n; -const twoLMask = (1n << twoL) - 1n; +const l2Mask = (1n << L2) - 1n; /** * Asserts that x, y, z \in [0, 2^88) @@ -89,9 +83,9 @@ function multiRangeCheck(x: Field, y: Field, z: Field) { function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { // constant case if (xy.isConstant() && z.isConstant()) { - if (xy.toBigInt() >> twoL || z.toBigInt() >> L) { + if (xy.toBigInt() >> L2 || z.toBigInt() >> L) { throw Error( - `Expected fields to fit in ${twoL} and ${L} bits respectively, got ${xy}, ${z}` + `Expected fields to fit in ${L2} and ${L} bits respectively, got ${xy}, ${z}` ); } let [x, y] = splitCompactLimb(xy.toBigInt()); From 99628cb01d0515805943ad71aacfbec4f117bc10 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 3 Nov 2023 00:29:10 +0100 Subject: [PATCH 0540/1215] raw gate wrapper --- src/lib/gates.ts | 12 +++++++++++- src/snarky.d.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index e18ad6e2d5..5d620066c5 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,4 +1,4 @@ -import { Snarky } from '../snarky.js'; +import { KimchiGateType, Snarky } from '../snarky.js'; import { FieldConst, type Field } from './field.js'; import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; @@ -23,6 +23,8 @@ const Gates = { rotate, generic, foreignFieldAdd, + foreignFieldMul, + raw, }; function rangeCheck0( @@ -230,3 +232,11 @@ function foreignFieldMul(inputs: { MlTuple.mapTo(negForeignFieldModulus, FieldConst.fromBigint) ); } + +function raw(kind: KimchiGateType, values: Field[], coefficients: bigint[]) { + Snarky.gates.raw( + kind, + MlArray.to(values.map((x) => x.value)), + MlArray.to(coefficients.map(FieldConst.fromBigint)) + ); +} diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 0408c637d5..69fe3bcb37 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -33,6 +33,7 @@ export { Snarky, Test, JsonGate, + KimchiGateType, MlPublicKey, MlPublicKeyVar, FeatureFlags, From 409c73ab64d4f76f9ffca2a23ab5ac20a5471ce8 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 3 Nov 2023 00:29:51 +0100 Subject: [PATCH 0541/1215] ffmul constraints --- src/lib/gadgets/foreign-field.ts | 76 +++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 0dfe5966c1..00b3cd5940 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -3,7 +3,15 @@ import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; import { assert, bitSlice, exists } from './common.js'; -import { L, lMask, multiRangeCheck, L2, l2Mask, L3 } from './range-check.js'; +import { + L, + lMask, + multiRangeCheck, + L2, + l2Mask, + L3, + compactMultiRangeCheck, +} from './range-check.js'; export { ForeignField, Field3, Sign }; @@ -21,6 +29,10 @@ const ForeignField = { }, sumChain, + mul(x: Field3, y: Field3, f: bigint) { + return multiply(x, y, f); + }, + // helper methods from(x: bigint): Field3 { return Field3(split(x)); @@ -97,20 +109,21 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return { result: [r0, r1, r2] satisfies Field3, overflow }; } -function multiply(aF: Field3, bF: Field3, f: bigint) { +function multiply(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); let f_ = (1n << L3) - f; let [f_0, f_1, f_2] = split(f_); + let f2 = f >> L2; + let f2Bound = (1n << L) - f2 - 1n; - let witnesses = exists(27, () => { + let witnesses = exists(21, () => { // split inputs into 3 limbs - let [a0, a1, a2] = bigint3(aF); - let [b0, b1, b2] = bigint3(bF); - let a = collapse([a0, a1, a2]); - let b = collapse([b0, b1, b2]); + let [a0, a1, a2] = bigint3(a); + let [b0, b1, b2] = bigint3(b); // compute q and r such that a*b = q*f + r - let ab = a * b; + let ab = collapse([a0, a1, a2]) * collapse([b0, b1, b2]); let q = ab / f; let r = ab - q * f; @@ -146,12 +159,10 @@ function multiply(aF: Field3, bF: Field3, f: bigint) { let c1_90 = bitSlice(c1, 90, 1); // quotient high bound - let q2Bound = q2 + (1n << L) - (f >> L2) - 1n; + let q2Bound = q2 + f2Bound; // prettier-ignore return [ - a0, a1, a2, - b0, b1, b2, r01, r2, q0, q1, q2, q2Bound, @@ -161,6 +172,49 @@ function multiply(aF: Field3, bF: Field3, f: bigint) { c1_84, c1_86, c1_88, c1_90, ]; }); + + // prettier-ignore + let [ + r01, r2, + q0, q1, q2, + q2Bound, + p10, p110, p111, + c0, + c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72, + c1_84, c1_86, c1_88, c1_90, + ] = witnesses; + + let q: Field3 = [q0, q1, q2]; + + // ffmul gate. this already adds the following zero row. + Gates.foreignFieldMul({ + left: a, + right: b, + remainder: [r01, r2], + quotient: q, + quotientHiBound: q2Bound, + product1: [p10, p110, p111], + carry0: c0, + carry1p: [c1_00, c1_12, c1_24, c1_36, c1_48, c1_60, c1_72], + carry1c: [c1_84, c1_86, c1_88, c1_90], + foreignFieldModulus2: f2, + negForeignFieldModulus: [f_0, f_1, f_2], + }); + + // limb range checks on quotient and remainder + multiRangeCheck(...q); + let r = compactMultiRangeCheck(r01, r2); + + // range check on q and r bounds + // TODO: this uses one RC too many.. need global RC stack + let [r2Bound, zero] = exists(2, () => [r2.toBigInt() + f2Bound, 0n]); + multiRangeCheck(q2Bound, r2Bound, zero); + + // constrain r2 bound, zero + r2.add(f2Bound).assertEquals(r2Bound); + zero.assertEquals(0); + + return [r, q] satisfies [Field3, Field3]; } function Field3(x: bigint3): Field3 { From 8f585772c0ee59698698e6368253e7aa8ac2fab4 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 3 Nov 2023 00:39:26 +0100 Subject: [PATCH 0542/1215] constant case --- src/lib/gadgets/foreign-field.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 00b3cd5940..d9052def29 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -109,7 +109,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return { result: [r0, r1, r2] satisfies Field3, overflow }; } -function multiply(a: Field3, b: Field3, f: bigint) { +function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); let f_ = (1n << L3) - f; @@ -117,6 +117,14 @@ function multiply(a: Field3, b: Field3, f: bigint) { let f2 = f >> L2; let f2Bound = (1n << L) - f2 - 1n; + // constant case + if (a.every((x) => x.isConstant()) && b.every((x) => x.isConstant())) { + let ab = ForeignField.toBigint(a) * ForeignField.toBigint(b); + let q = ab / f; + let r = ab - q * f; + return [ForeignField.from(r), ForeignField.from(q)]; + } + let witnesses = exists(21, () => { // split inputs into 3 limbs let [a0, a1, a2] = bigint3(a); @@ -214,7 +222,7 @@ function multiply(a: Field3, b: Field3, f: bigint) { r2.add(f2Bound).assertEquals(r2Bound); zero.assertEquals(0); - return [r, q] satisfies [Field3, Field3]; + return [r, q]; } function Field3(x: bigint3): Field3 { From 283fbd6c86178511e99c1c80dd2fc339380b63a0 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 3 Nov 2023 00:39:38 +0100 Subject: [PATCH 0543/1215] start testing --- src/lib/gadgets/foreign-field.unit-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 0c173c8352..baac7ca8e0 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -50,6 +50,7 @@ for (let F of fields) { eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); + eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus)[0], 'mul'); // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( From 9a993525f20390d7c5fa1f4fe46e1610fca5d946 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 00:46:01 -0700 Subject: [PATCH 0544/1215] docs(gadgets.ts): add mina book implementation refernence links for rotate and xor gadgets --- src/lib/gadgets/gadgets.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 1c5c2d5e88..0a82172680 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -50,6 +50,8 @@ const Gadgets = { * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * * @param field {@link Field} element to rotate. * @param bits amount of bits to rotate this {@link Field} element with. * @param direction left or right rotation direction. @@ -88,6 +90,8 @@ const Gadgets = { * * For example, with `length = 2` (`paddedLength = 16`), `xor()` will fail for any input that is larger than `2**16`. * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * * @param a {@link Field} element to compare. * @param b {@link Field} element to compare. * @param length amount of bits to compare. @@ -113,7 +117,7 @@ const Gadgets = { * Web/JavaScript/Reference/Operators/Bitwise_NOT). * * **Note:** The NOT gate only operates over the amount - * of bits specified by the 'length' parameter. + * of bits specified by the `length` parameter. * * A NOT gate works by returning `1` in each bit position if the * corresponding bit of the operand is `0`, and returning `0` if the @@ -123,7 +127,6 @@ const Gadgets = { * * **Note:** Specifying a larger `length` parameter adds additional constraints. * - * * NOT is implemented in two different ways. If the `checked` parameter is set to `true` * the {@link Gadgets.xor} gadget is reused with a second argument to be an * all one bitmask the same length. This approach needs as many rows as an XOR would need @@ -150,8 +153,9 @@ const Gadgets = { * * @param a - The value to apply NOT to. * @param length - The number of bits to be considered for the NOT operation. - * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it is set to `true` the {@link Gadgets.xor} gadget is reused. - * If it is set to `false`, NOT is implemented as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it + * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented + * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. * */ not(a: Field, length: number, checked: boolean = false) { From fb28f8801cc080d435c2b077eb02dc63141cd73f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 08:55:23 +0100 Subject: [PATCH 0545/1215] another small witness test --- src/lib/gadgets/foreign-field.unit-test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index baac7ca8e0..13b548ad06 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -51,6 +51,11 @@ for (let F of fields) { eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus)[0], 'mul'); + eq2( + (x, y) => (x * y) / F.modulus, + (x, y) => ForeignField.mul(x, y, F.modulus)[1], + 'mul quotient' + ); // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( @@ -99,7 +104,7 @@ let ffProgram = ZkProgram({ await ffProgram.compile(); -await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 5 })( +await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 0 })( (xs) => sumchain(xs, signs, F), async (xs) => { let proof = await ffProgram.sumchain(xs); From c28cf2eebef24849449baab28ef08a4995f9308b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 01:23:34 -0700 Subject: [PATCH 0546/1215] docs(gadgets): update the documentation to mention the operation will fail if the length is larger than 254 --- src/lib/gadgets/bitwise.unit-test.ts | 4 ++-- src/lib/gadgets/gadgets.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 24e348e4a8..b7332484d0 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -33,13 +33,13 @@ let Bitwise = ZkProgram({ notUnchecked: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 255, false); + return Gadgets.not(a, 254, false); }, }, notChecked: { privateInputs: [Field], method(a: Field) { - return Gadgets.not(a, 255, true); + return Gadgets.not(a, 254, true); }, }, and: { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0a82172680..fab86d5e88 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -125,7 +125,7 @@ const Gadgets = { * * The `length` parameter lets you define how many bits to NOT. * - * **Note:** Specifying a larger `length` parameter adds additional constraints. + * **Note:** Specifying a larger `length` parameter adds additional constraints. The operation will fail if the length is larger than 254. * * NOT is implemented in two different ways. If the `checked` parameter is set to `true` * the {@link Gadgets.xor} gadget is reused with a second argument to be an From ee17ca3acebc0a1cc044afd7cb3e7a741589f8a0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 09:58:48 +0100 Subject: [PATCH 0547/1215] add proof mul test which doesn't work --- src/lib/gadgets/foreign-field.unit-test.ts | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 13b548ad06..69cce14188 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -99,12 +99,24 @@ let ffProgram = ZkProgram({ return ForeignField.sumChain(xs, signs, F.modulus); }, }, + + mul: { + privateInputs: [Field3_, Field3_], + method(x, y) { + let [r, _q] = ForeignField.mul(x, y, F.modulus); + return r; + }, + }, }, }); await ffProgram.compile(); -await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 0 })( +await equivalentAsync( + { from: [array(f, chainLength)], to: f }, + // TODO revert to 3 runs + { runs: 0 } +)( (xs) => sumchain(xs, signs, F), async (xs) => { let proof = await ffProgram.sumchain(xs); @@ -114,6 +126,16 @@ await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 0 })( 'prove chain' ); +await equivalentAsync({ from: [f, f], to: f }, { runs: 10 })( + F.mul, + async (x, y) => { + let proof = await ffProgram.mul(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove mul' +); + // helper function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { From c98aa3e0e4ca917aa37668b70cd7ee9f5718cdf2 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 02:02:41 -0700 Subject: [PATCH 0548/1215] feat(CHANGELOG.md): add entry for new `Gadgets.not()` method The `Gadgets.not()` method was added to support bitwise shifting for native field elements. This change was made in response to the pull request #1198 on the o1js repository. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b64391d853..7f8109d1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- `Gadgets.not()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1198 - `Gadgets.leftShift() / Gadgets.rightShift()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1194 - `Gadgets.and()`, new provable method to support bitwise and for native field elements. https://github.com/o1-labs/o1js/pull/1193 From 8983d3e3ed51638b1720db01230b88a41bd4b84e Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Fri, 3 Nov 2023 11:25:20 +0100 Subject: [PATCH 0549/1215] Fixed the Escrow smart contract example --- src/examples/zkapps/escrow/escrow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/escrow/escrow.ts b/src/examples/zkapps/escrow/escrow.ts index a702a5f7a0..1917dca854 100644 --- a/src/examples/zkapps/escrow/escrow.ts +++ b/src/examples/zkapps/escrow/escrow.ts @@ -11,7 +11,7 @@ export class Escrow extends SmartContract { // add your deposit logic circuit here // that will adjust the amount - const payerUpdate = AccountUpdate.create(user); + const payerUpdate = AccountUpdate.createSigned(user); payerUpdate.send({ to: this.address, amount: UInt64.from(1000000) }); } From f3d189e9a1e6117c1d69f974aa0e48cc51737052 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 11:56:22 +0100 Subject: [PATCH 0550/1215] fixup 'party witness' example --- src/examples/party-witness.ts | 73 +++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/examples/party-witness.ts b/src/examples/party-witness.ts index a33bdac352..f6c15d7956 100644 --- a/src/examples/party-witness.ts +++ b/src/examples/party-witness.ts @@ -1,13 +1,5 @@ -import { - AccountUpdate, - PrivateKey, - Circuit, - provable, - isReady, - Provable, -} from 'o1js'; - -await isReady; +import assert from 'assert/strict'; +import { AccountUpdate, PrivateKey, Provable, Empty } from 'o1js'; let address = PrivateKey.random().toPublicKey(); @@ -24,16 +16,21 @@ let aux = AccountUpdate.toAuxiliary(accountUpdate); let accountUpdateRaw = AccountUpdate.fromFields(fields, aux); let json = AccountUpdate.toJSON(accountUpdateRaw); -if (address.toBase58() !== json.body.publicKey) throw Error('fail'); - -let Null = provable(null); +assert.equal( + address.toBase58(), + json.body.publicKey, + 'recover json public key' +); Provable.runAndCheck(() => { - let accountUpdateWitness = AccountUpdate.witness(Null, () => ({ - accountUpdate, - result: null, - })).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 5); + let accountUpdateWitness = Provable.witness( + AccountUpdate, + () => accountUpdate + ); + assert( + accountUpdateWitness.body.callDepth === 5, + 'when witness block is executed, witness() recreates auxiliary parts of provable type' + ); Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); Provable.assertEqual( PrivateKey, @@ -42,12 +39,15 @@ Provable.runAndCheck(() => { ); }); -let result = Provable.witness(() => { - let accountUpdateWitness = AccountUpdate.witness(Null, () => ({ - accountUpdate, - result: null, - })).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 0); +let result = Provable.constraintSystem(() => { + let accountUpdateWitness = Provable.witness( + AccountUpdate, + () => accountUpdate + ); + assert( + accountUpdateWitness.body.callDepth === 0, + 'when witness block is not executed, witness() returns dummy data' + ); Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); }); @@ -56,19 +56,26 @@ console.log( `witnessing an account update and comparing it to another one creates ${result.rows} rows` ); -result = Provable.witness(() => { - let accountUpdateWitness = AccountUpdate.witness( - Null, - () => ({ - accountUpdate, - result: null, - }), +result = Provable.constraintSystem(() => { + let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( + Empty, + () => ({ accountUpdate, result: undefined }), { skipCheck: true } - ).accountUpdate; - console.assert(accountUpdateWitness.body.callDepth === 0); + ); Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); }); console.log( `without all the checks on subfields, witnessing and comparing only creates ${result.rows} rows` ); + +result = Provable.constraintSystem(() => { + let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( + Empty, + () => ({ accountUpdate, result: undefined }), + { skipCheck: true } + ); + accountUpdateWitness.hash(); +}); + +console.log(`hashing a witnessed account update creates ${result.rows} rows`); From 56e352daed6d5e2f20efc920daaf1bac740ffa72 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 13:40:21 +0100 Subject: [PATCH 0551/1215] make party-witness example good --- src/examples/party-witness.ts | 114 +++++++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 14 deletions(-) diff --git a/src/examples/party-witness.ts b/src/examples/party-witness.ts index f6c15d7956..6f7af3a68a 100644 --- a/src/examples/party-witness.ts +++ b/src/examples/party-witness.ts @@ -1,8 +1,24 @@ +/** + * This example explains some inner workings of provable types at the hand of a particularly + * complex type: `AccountUpdate`. + */ import assert from 'assert/strict'; -import { AccountUpdate, PrivateKey, Provable, Empty } from 'o1js'; +import { + AccountUpdate, + PrivateKey, + Provable, + Empty, + ProvableExtended, +} from 'o1js'; +import { expect } from 'expect'; -let address = PrivateKey.random().toPublicKey(); +/** + * Example of a complex provable type: `AccountUpdate` + */ +AccountUpdate satisfies Provable; +console.log(`an account update has ${AccountUpdate.sizeInFields()} fields`); +let address = PrivateKey.random().toPublicKey(); let accountUpdate = AccountUpdate.defaultAccountUpdate(address); accountUpdate.body.callDepth = 5; accountUpdate.lazyAuthorization = { @@ -10,28 +26,66 @@ accountUpdate.lazyAuthorization = { privateKey: PrivateKey.random(), }; +/** + * Every provable type can be disassembled into its provable/in-circuit part (fields) + * and a non-provable part (auxiliary). + * + * The parts can be assembled back together to create a new object which is deeply equal to the old one. + */ let fields = AccountUpdate.toFields(accountUpdate); let aux = AccountUpdate.toAuxiliary(accountUpdate); +let accountUpdateRecovered = AccountUpdate.fromFields(fields, aux); +expect(accountUpdateRecovered.body).toEqual(accountUpdate.body); +expect(accountUpdateRecovered.lazyAuthorization).toEqual( + accountUpdate.lazyAuthorization +); -let accountUpdateRaw = AccountUpdate.fromFields(fields, aux); -let json = AccountUpdate.toJSON(accountUpdateRaw); - -assert.equal( - address.toBase58(), - json.body.publicKey, - 'recover json public key' +/** + * Provable types which implement `ProvableExtended` can also be serialized to/from JSON. + * + * However, `AccountUpdate` specifically is a wrapper around an actual, core provable extended type. + * It has additional properties, like lazySignature, which are not part of the JSON representation + * and therefore aren't recovered. + */ +AccountUpdate satisfies ProvableExtended; +let json = AccountUpdate.toJSON(accountUpdate); +accountUpdateRecovered = AccountUpdate.fromJSON(json); +expect(accountUpdateRecovered.body).toEqual(accountUpdate.body); +expect(accountUpdateRecovered.lazyAuthorization).not.toEqual( + accountUpdate.lazyAuthorization ); +/** + * Provable.runAndCheck() can be used to run a circuit in "prover mode". + * That means + * -) witness() and asProver() blocks are excuted + * -) constraints are checked; failing assertions throw an error + */ Provable.runAndCheck(() => { + /** + * Provable.witness() is used to introduce all values to the circuit which are not hard-coded constants. + * + * Under the hood, it disassembles and reassembles the provable type with toFields(), toAuxiliary() and fromFields(). + */ let accountUpdateWitness = Provable.witness( AccountUpdate, () => accountUpdate ); + + /** + * The witness is "provably equal" to the original. + * (this, under hood, calls assertEqual on all fields returned by .toFields()). + */ + Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); + + /** + * Auxiliary parts are also recovered in the witness. + * Note, though, that this can't be enforced as part of a proof! + */ assert( accountUpdateWitness.body.callDepth === 5, 'when witness block is executed, witness() recreates auxiliary parts of provable type' ); - Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); Provable.assertEqual( PrivateKey, (accountUpdateWitness.lazyAuthorization as any).privateKey, @@ -39,11 +93,29 @@ Provable.runAndCheck(() => { ); }); +/** + * Provable.constraintSystem() runs the circuit in "compile mode". + * -) witness() and asProver() blocks are not executed + * -) fields don't have actual values attached to them; they're purely abstract variables + * -) constraints are not checked + */ let result = Provable.constraintSystem(() => { + /** + * In compile mode, witness() returns + * - abstract variables without values for fields + * - dummy data for auxiliary + */ let accountUpdateWitness = Provable.witness( AccountUpdate, - () => accountUpdate + (): AccountUpdate => { + throw 'not executed anyway'; + } ); + + /** + * Dummy data can take a different form depending on the provable type, + * but in most cases it's "all-zeroes" + */ assert( accountUpdateWitness.body.callDepth === 0, 'when witness block is not executed, witness() returns dummy data' @@ -51,11 +123,23 @@ let result = Provable.constraintSystem(() => { Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); }); -console.log(`an account update has ${AccountUpdate.sizeInFields()} fields`); +/** + * Provable.constraintSystem() is a great way to investigate how many constraints operations take. + * + * Note that even just witnessing stuff takes constraints, for provable types which define a check() method. + * Bools are proved to be 0 or 1, UInt64 is proved to be within [0, 2^64), etc. + */ console.log( `witnessing an account update and comparing it to another one creates ${result.rows} rows` ); +/** + * For account updates specifically, we typically don't want all the subfield checks. That's because + * account updates are usually tied the _public input_. The public input is checked on the verifier side + * already, including the well-formedness of its parts, so there's no need to include that in the proof. + * + * This is why we have this custom way of witnessing account updates, with the `skipCheck` option. + */ result = Provable.constraintSystem(() => { let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( Empty, @@ -64,11 +148,14 @@ result = Provable.constraintSystem(() => { ); Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); }); - console.log( `without all the checks on subfields, witnessing and comparing only creates ${result.rows} rows` ); +/** + * To relate an account update to the hash which is the public input, we need to perform the hash in-circuit. + * This is takes several 100 constraints, and is basically the minimal size of a zkApp method. + */ result = Provable.constraintSystem(() => { let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( Empty, @@ -77,5 +164,4 @@ result = Provable.constraintSystem(() => { ); accountUpdateWitness.hash(); }); - console.log(`hashing a witnessed account update creates ${result.rows} rows`); From a1dfdfdacc841cc73a1ff68cb570b7bdca8ee78f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 13:41:21 +0100 Subject: [PATCH 0552/1215] move example to new folder and better name --- .../{party-witness.ts => internals/advanced-provable-types.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/examples/{party-witness.ts => internals/advanced-provable-types.ts} (100%) diff --git a/src/examples/party-witness.ts b/src/examples/internals/advanced-provable-types.ts similarity index 100% rename from src/examples/party-witness.ts rename to src/examples/internals/advanced-provable-types.ts From 052ccbb94f47b1fa1817b16fe81a9c293f4a7696 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 13:43:35 +0100 Subject: [PATCH 0553/1215] add subfolder readme --- src/examples/internals/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/examples/internals/README.md diff --git a/src/examples/internals/README.md b/src/examples/internals/README.md new file mode 100644 index 0000000000..ddc6d96fe6 --- /dev/null +++ b/src/examples/internals/README.md @@ -0,0 +1,5 @@ +# Examples: Internals + +This folder contains examples which highlight inner workings and less-documented behaviours of o1js. + +These examples might be useful for advanced users and contributors. From 7c05679897a4164a22341d908c141ed031f0e151 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 5 Oct 2023 08:12:12 +0200 Subject: [PATCH 0554/1215] use module: nodenext to satisfy tsc --- src/build/buildExample.js | 8 +++++++- tsconfig.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/build/buildExample.js b/src/build/buildExample.js index b58cda4e2c..2f3a37e374 100644 --- a/src/build/buildExample.js +++ b/src/build/buildExample.js @@ -18,6 +18,9 @@ async function buildAndImport(srcPath, { keepFile = false }) { async function build(srcPath, isWeb = false) { let tsConfig = findTsConfig() ?? defaultTsConfig; + // TODO hack because ts.transpileModule doesn't treat module = 'nodenext' correctly + // but `tsc` demands it to be `nodenext` + tsConfig.compilerOptions.module = 'esnext'; let outfile = srcPath.replace('.ts', '.tmp.js'); @@ -46,6 +49,9 @@ async function build(srcPath, isWeb = false) { async function buildOne(srcPath) { let tsConfig = findTsConfig() ?? defaultTsConfig; + // TODO hack because ts.transpileModule doesn't treat module = 'nodenext' correctly + // but `tsc` demands it to be `nodenext` + tsConfig.compilerOptions.module = 'esnext'; let outfile = path.resolve( './dist/node', @@ -74,7 +80,7 @@ const defaultTsConfig = { target: 'esnext', importHelpers: true, strict: true, - moduleResolution: 'node', + moduleResolution: 'nodenext', esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, diff --git a/tsconfig.json b/tsconfig.json index 569ef2b1e6..0d67746964 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "outDir": "dist", "baseUrl": ".", // affects where output files end up "target": "es2021", // goal: ship *the most modern syntax* that is supported by *all* browsers that support our Wasm - "module": "es2022", // allow top-level await + "module": "nodenext", // allow top-level await "moduleResolution": "nodenext", // comply with node + "type": "module" "esModuleInterop": true, // to silence jest From 3b1116d63b201eb04cdb6e67124d3db0b6de08c4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 13:58:42 +0100 Subject: [PATCH 0555/1215] create folder for utils used in examples --- src/examples/benchmarks/hash-witness.ts | 2 +- src/examples/benchmarks/mul-web.ts | 2 +- src/examples/benchmarks/mul-witness.ts | 2 +- src/examples/benchmarks/mul.ts | 2 +- src/examples/utils/README.md | 1 + .../{zkapps/tictoc.ts => utils/tic-toc.node.ts} | 10 +++++++--- src/examples/{benchmarks => utils}/tic-toc.ts | 10 ++++++++-- src/examples/zkapps/dex/happy-path-with-actions.ts | 2 +- src/examples/zkapps/dex/happy-path-with-proofs.ts | 2 +- src/examples/zkapps/dex/run-berkeley.ts | 2 +- src/tests/inductive-proofs-small.ts | 2 +- src/tests/inductive-proofs.ts | 2 +- 12 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 src/examples/utils/README.md rename src/examples/{zkapps/tictoc.ts => utils/tic-toc.node.ts} (52%) rename src/examples/{benchmarks => utils}/tic-toc.ts (61%) diff --git a/src/examples/benchmarks/hash-witness.ts b/src/examples/benchmarks/hash-witness.ts index 7e87e96917..08adaf87f7 100644 --- a/src/examples/benchmarks/hash-witness.ts +++ b/src/examples/benchmarks/hash-witness.ts @@ -2,7 +2,7 @@ * benchmark witness generation for an all-mul circuit */ import { Field, Provable, Poseidon } from 'o1js'; -import { tic, toc } from './tic-toc.js'; +import { tic, toc } from '../utils/tic-toc.js'; // parameters let nPermutations = 1 << 12; // 2^12 x 11 rows < 2^16 rows, should just fit in a circuit diff --git a/src/examples/benchmarks/mul-web.ts b/src/examples/benchmarks/mul-web.ts index e2b39ff9c8..43c977ff74 100644 --- a/src/examples/benchmarks/mul-web.ts +++ b/src/examples/benchmarks/mul-web.ts @@ -2,7 +2,7 @@ * benchmark a circuit filled with generic gates */ import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; -import { tic, toc } from './tic-toc.js'; +import { tic, toc } from '../utils/tic-toc.js'; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows diff --git a/src/examples/benchmarks/mul-witness.ts b/src/examples/benchmarks/mul-witness.ts index dd4b164929..17e2165fbd 100644 --- a/src/examples/benchmarks/mul-witness.ts +++ b/src/examples/benchmarks/mul-witness.ts @@ -2,7 +2,7 @@ * benchmark witness generation for an all-mul circuit */ import { Field, Provable } from 'o1js'; -import { tic, toc } from './tic-toc.js'; +import { tic, toc } from '../utils/tic-toc.js'; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows diff --git a/src/examples/benchmarks/mul.ts b/src/examples/benchmarks/mul.ts index deb9aabe43..3f92f8c27c 100644 --- a/src/examples/benchmarks/mul.ts +++ b/src/examples/benchmarks/mul.ts @@ -2,7 +2,7 @@ * benchmark a circuit filled with generic gates */ import { Circuit, Field, Provable, circuitMain, ZkProgram } from 'o1js'; -import { tic, toc } from '../zkapps/tictoc.js'; +import { tic, toc } from '../utils/tic-toc.node.js'; // parameters let nMuls = (1 << 16) + (1 << 15); // not quite 2^17 generic gates = not quite 2^16 rows diff --git a/src/examples/utils/README.md b/src/examples/utils/README.md new file mode 100644 index 0000000000..5a990bb239 --- /dev/null +++ b/src/examples/utils/README.md @@ -0,0 +1 @@ +This folder doesn't contain stand-alone examples, but utilities used in our examples. diff --git a/src/examples/zkapps/tictoc.ts b/src/examples/utils/tic-toc.node.ts similarity index 52% rename from src/examples/zkapps/tictoc.ts rename to src/examples/utils/tic-toc.node.ts index 35bd2de501..9e8d50634a 100644 --- a/src/examples/zkapps/tictoc.ts +++ b/src/examples/utils/tic-toc.node.ts @@ -1,4 +1,8 @@ -// helper for printing timings +/** + * Helper for printing timings, in the spirit of Python's `tic` and `toc`. + * + * This is a slightly nicer version of './tic-tic.ts' which only works in Node. + */ export { tic, toc }; @@ -7,11 +11,11 @@ let i = 0; function tic(label = `Run command ${i++}`) { process.stdout.write(`${label}... `); - timingStack.push([label, Date.now()]); + timingStack.push([label, performance.now()]); } function toc() { let [label, start] = timingStack.pop()!; - let time = (Date.now() - start) / 1000; + let time = (performance.now() - start) / 1000; process.stdout.write(`\r${label}... ${time.toFixed(3)} sec\n`); } diff --git a/src/examples/benchmarks/tic-toc.ts b/src/examples/utils/tic-toc.ts similarity index 61% rename from src/examples/benchmarks/tic-toc.ts rename to src/examples/utils/tic-toc.ts index 9c037770cd..4b66514d8d 100644 --- a/src/examples/benchmarks/tic-toc.ts +++ b/src/examples/utils/tic-toc.ts @@ -1,14 +1,20 @@ +/** + * Helper for printing timings, in the spirit of Python's `tic` and `toc`. + */ + export { tic, toc }; let timingStack: [string, number][] = []; let i = 0; + function tic(label = `Run command ${i++}`) { console.log(`${label}... `); - timingStack.push([label, Date.now()]); + timingStack.push([label, performance.now()]); } + function toc() { let [label, start] = timingStack.pop()!; - let time = (Date.now() - start) / 1000; + let time = (performance.now() - start) / 1000; console.log(`\r${label}... ${time.toFixed(3)} sec\n`); return time; } diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index e3d4ce5be1..52eab4f2a0 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -9,7 +9,7 @@ import { } from './dex-with-actions.js'; import { TokenContract } from './dex.js'; import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; await isReady; diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 7e81df9c44..9e78f3b7e8 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -1,7 +1,7 @@ import { isReady, Mina, AccountUpdate, UInt64 } from 'o1js'; import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; import { getProfiler } from '../../profiler.js'; await isReady; diff --git a/src/examples/zkapps/dex/run-berkeley.ts b/src/examples/zkapps/dex/run-berkeley.ts index 1a15eaaaba..540382bd67 100644 --- a/src/examples/zkapps/dex/run-berkeley.ts +++ b/src/examples/zkapps/dex/run-berkeley.ts @@ -15,7 +15,7 @@ import { } from './dex-with-actions.js'; import { TokenContract } from './dex.js'; import { expect } from 'expect'; -import { tic, toc } from '../tictoc.js'; +import { tic, toc } from '../../utils/tic-toc.node.js'; await isReady; diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index d24f741302..2441c0030d 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -6,7 +6,7 @@ import { shutdown, Proof, } from '../index.js'; -import { tic, toc } from '../examples/zkapps/tictoc.js'; +import { tic, toc } from '../examples/utils/tic-toc.node.js'; await isReady; diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index 7fa724d162..e60eee018a 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -6,7 +6,7 @@ import { shutdown, Proof, } from '../index.js'; -import { tic, toc } from '../examples/zkapps/tictoc.js'; +import { tic, toc } from '../examples/utils/tic-toc.node.js'; await isReady; From cff7732ff93d72ac05fa7926274ae4959b546d98 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 15:40:05 +0100 Subject: [PATCH 0556/1215] revert test to normal --- src/lib/gadgets/foreign-field.unit-test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 69cce14188..d62235d411 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -112,11 +112,7 @@ let ffProgram = ZkProgram({ await ffProgram.compile(); -await equivalentAsync( - { from: [array(f, chainLength)], to: f }, - // TODO revert to 3 runs - { runs: 0 } -)( +await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 3 })( (xs) => sumchain(xs, signs, F), async (xs) => { let proof = await ffProgram.sumchain(xs); @@ -126,7 +122,7 @@ await equivalentAsync( 'prove chain' ); -await equivalentAsync({ from: [f, f], to: f }, { runs: 10 })( +await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( F.mul, async (x, y) => { let proof = await ffProgram.mul(x, y); From 1a666e86155275d731d59afd464164e351fc91b3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 15:50:51 +0100 Subject: [PATCH 0557/1215] match doc style of other gadgets and tweak function signature --- src/lib/gadgets/gadgets.ts | 22 ++++++++++++++++++---- src/lib/gadgets/range-check.ts | 8 ++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 570cd7b211..ab010921b0 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -221,9 +221,16 @@ const Gadgets = { * * In particular, the 3x88-bit range check supports bigints up to 264 bits, which in turn is enough * to support foreign field multiplication with moduli up to 2^259. + * + * @example + * ```ts + * Gadgets.multiRangeCheck([x, y, z]); + * ``` + * + * @throws Throws an error if one of the input values exceeds 88 bits. */ - multiRangeCheck(x: Field, y: Field, z: Field) { - multiRangeCheck(x, y, z); + multiRangeCheck(limbs: [Field, Field, Field]) { + multiRangeCheck(limbs); }, /** @@ -238,8 +245,15 @@ const Gadgets = { * - proves that x, y, z are all in the range [0, 2^88). * * The split form [x, y, z] is returned. + * + * @example + * ```ts + * let [x, y] = Gadgets.compactMultiRangeCheck([xy, z]); + * ``` + * + * @throws Throws an error if `xy` exceeds 2*88 = 176 bits, or if z exceeds 88 bits. */ - compactMultiRangeCheck(xy: Field, z: Field) { - return compactMultiRangeCheck(xy, z); + compactMultiRangeCheck(limbs: [Field, Field]) { + return compactMultiRangeCheck(limbs); }, }; diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index d48356d480..bc806f430e 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -57,7 +57,7 @@ const lMask = (1n << L) - 1n; /** * Asserts that x, y, z \in [0, 2^88) */ -function multiRangeCheck(x: Field, y: Field, z: Field) { +function multiRangeCheck([x, y, z]: [Field, Field, Field]) { if (x.isConstant() && y.isConstant() && z.isConstant()) { if (x.toBigInt() >> L || y.toBigInt() >> L || z.toBigInt() >> L) { throw Error(`Expected fields to fit in ${L} bits, got ${x}, ${y}, ${z}`); @@ -77,7 +77,11 @@ function multiRangeCheck(x: Field, y: Field, z: Field) { * * Returns the full limbs x, y, z */ -function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { +function compactMultiRangeCheck([xy, z]: [Field, Field]): [ + Field, + Field, + Field +] { // constant case if (xy.isConstant() && z.isConstant()) { if (xy.toBigInt() >> twoL || z.toBigInt() >> L) { From 9a594c902d40d221f2349ac6831b5e5a935fc529 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 15:54:59 +0100 Subject: [PATCH 0558/1215] revert compact rc signature tweak, and fixup test --- src/lib/gadgets/gadgets.ts | 4 ++-- src/lib/gadgets/range-check.ts | 6 +----- src/lib/gadgets/range-check.unit-test.ts | 6 +++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index ab010921b0..00e11a7a72 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -253,7 +253,7 @@ const Gadgets = { * * @throws Throws an error if `xy` exceeds 2*88 = 176 bits, or if z exceeds 88 bits. */ - compactMultiRangeCheck(limbs: [Field, Field]) { - return compactMultiRangeCheck(limbs); + compactMultiRangeCheck(xy: Field, z: Field) { + return compactMultiRangeCheck(xy, z); }, }; diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index bc806f430e..1f4f369157 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -77,11 +77,7 @@ function multiRangeCheck([x, y, z]: [Field, Field, Field]) { * * Returns the full limbs x, y, z */ -function compactMultiRangeCheck([xy, z]: [Field, Field]): [ - Field, - Field, - Field -] { +function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { // constant case if (xy.isConstant() && z.isConstant()) { if (xy.toBigInt() >> twoL || z.toBigInt() >> L) { diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 0796183871..c66c6a806d 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -38,8 +38,8 @@ let check64 = Provable.constraintSystem(() => { Gadgets.rangeCheck64(x); }); let multi = Provable.constraintSystem(() => { - let [x, y, z] = exists(3, () => [0n, 0n, 0n]); - Gadgets.multiRangeCheck(x, y, z); + let x = exists(3, () => [0n, 0n, 0n]); + Gadgets.multiRangeCheck(x); }); let compact = Provable.constraintSystem(() => { let [xy, z] = exists(2, () => [0n, 0n]); @@ -70,7 +70,7 @@ let RangeCheck = ZkProgram({ checkMulti: { privateInputs: [Field, Field, Field], method(x, y, z) { - Gadgets.multiRangeCheck(x, y, z); + Gadgets.multiRangeCheck([x, y, z]); }, }, checkCompact: { From b02fac085546b4a44f37fa8dc3d398718678d33f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:09:10 +0100 Subject: [PATCH 0559/1215] examples readme --- src/examples/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/examples/README.md diff --git a/src/examples/README.md b/src/examples/README.md new file mode 100644 index 0000000000..24add4d6b8 --- /dev/null +++ b/src/examples/README.md @@ -0,0 +1,25 @@ +# o1js Examples + +This folder contains many examples for using o1js. Take a look around! + +## Running examples + +You can run most examples using Node.js from the root directory, using the `./run` script: + +``` +./run src/examples/some-example.ts +``` + +Some examples depend on other files in addition to `"o1js"`. For those examples, you need to add the `--bundle` option to bundle them before running: + +``` +./run src/examples/multi-file-example.ts --bundle +``` + +Most of the examples do not depend on Node.js specific APIs, and can also be run in a browser. To do so, use: + +``` +./run-in-browser.js src/examples/web-compatible-example.ts +``` + +After running the above, navigate to http://localhost:8000 and open your browser's developer console to see the example executing. From 0cb398d4104589111b507b40657e412e639af281 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:14:59 +0100 Subject: [PATCH 0560/1215] move sudoku, delete deploy --- src/examples/deploy/.gitignore | 1 - src/examples/deploy/compile.ts | 26 -------------- src/examples/deploy/deploy.ts | 36 ------------------- src/examples/deploy/simple_zkapp.ts | 19 ---------- src/examples/{ => zkapps}/sudoku/index.ts | 0 .../{ => zkapps}/sudoku/sudoku-lib.js | 0 src/examples/{ => zkapps}/sudoku/sudoku.ts | 0 7 files changed, 82 deletions(-) delete mode 100644 src/examples/deploy/.gitignore delete mode 100644 src/examples/deploy/compile.ts delete mode 100644 src/examples/deploy/deploy.ts delete mode 100644 src/examples/deploy/simple_zkapp.ts rename src/examples/{ => zkapps}/sudoku/index.ts (100%) rename src/examples/{ => zkapps}/sudoku/sudoku-lib.js (100%) rename src/examples/{ => zkapps}/sudoku/sudoku.ts (100%) diff --git a/src/examples/deploy/.gitignore b/src/examples/deploy/.gitignore deleted file mode 100644 index 378eac25d3..0000000000 --- a/src/examples/deploy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/src/examples/deploy/compile.ts b/src/examples/deploy/compile.ts deleted file mode 100644 index 5cf9dd9025..0000000000 --- a/src/examples/deploy/compile.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isReady, PrivateKey, shutdown } from 'o1js'; -import SimpleZkapp from './simple_zkapp.js'; -import { writeFileSync, mkdirSync, existsSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; - -await isReady; - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -let { verificationKey } = await SimpleZkapp.compile(); -storeArtifact(SimpleZkapp, { verificationKey }); - -shutdown(); - -function storeArtifact(SmartContract: Function, json: unknown) { - let thisFolder = dirname(fileURLToPath(import.meta.url)); - if (!existsSync(`${thisFolder}/build`)) { - mkdirSync(`${thisFolder}/build`); - } - writeFileSync( - `${thisFolder}/build/${SmartContract.name}.json`, - JSON.stringify(json) - ); -} diff --git a/src/examples/deploy/deploy.ts b/src/examples/deploy/deploy.ts deleted file mode 100644 index e820d4a020..0000000000 --- a/src/examples/deploy/deploy.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isReady, PrivateKey, shutdown, Mina } from 'o1js'; -import SimpleZkapp from './simple_zkapp.js'; -import { readFileSync, existsSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; - -await isReady; - -// TODO: get keys from somewhere else; for now we assume the account is already funded -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -// read verification key from disk -let artifact = readArtifact(SimpleZkapp); -if (artifact === undefined) - throw Error('No verification key found! Use compile.ts first'); -let { verificationKey } = artifact; - -// produce and log the transaction json; the fee payer is a dummy which has to be added later, by the signing logic -let tx = await Mina.transaction(() => { - new SimpleZkapp(zkappAddress).deploy({ verificationKey }); -}); -let transactionJson = tx.sign([zkappKey]).toJSON(); - -console.log(transactionJson); - -shutdown(); - -function readArtifact(SmartContract: Function) { - let thisFolder = dirname(fileURLToPath(import.meta.url)); - let jsonFile = `${thisFolder}/build/${SmartContract.name}.json`; - if (!existsSync(`${thisFolder}/build`) || !existsSync(jsonFile)) { - return undefined; - } - return JSON.parse(readFileSync(jsonFile, 'utf-8')); -} diff --git a/src/examples/deploy/simple_zkapp.ts b/src/examples/deploy/simple_zkapp.ts deleted file mode 100644 index 496a47cc9d..0000000000 --- a/src/examples/deploy/simple_zkapp.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Field, state, State, method, SmartContract } from 'o1js'; - -export { SimpleZkapp as default }; - -const initialState = 10; - -class SimpleZkapp extends SmartContract { - @state(Field) x = State(); - - init() { - super.init(); - this.x.set(Field(initialState)); - } - - @method update(y: Field) { - let x = this.x.get(); - this.x.set(x.add(y)); - } -} diff --git a/src/examples/sudoku/index.ts b/src/examples/zkapps/sudoku/index.ts similarity index 100% rename from src/examples/sudoku/index.ts rename to src/examples/zkapps/sudoku/index.ts diff --git a/src/examples/sudoku/sudoku-lib.js b/src/examples/zkapps/sudoku/sudoku-lib.js similarity index 100% rename from src/examples/sudoku/sudoku-lib.js rename to src/examples/zkapps/sudoku/sudoku-lib.js diff --git a/src/examples/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts similarity index 100% rename from src/examples/sudoku/sudoku.ts rename to src/examples/zkapps/sudoku/sudoku.ts From 1152d7c600b056b1a338755291152438e07a5b76 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:15:02 +0100 Subject: [PATCH 0561/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index e17660291a..49c3f0f309 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e17660291a884877639fdbdd66c6537b75855661 +Subproject commit 49c3f0f309c748f137a2c77d279a3a5e2b1918d6 From 5945f45382321026064a6f1d87d5b38dc090723a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:28:27 +0100 Subject: [PATCH 0562/1215] move vk-regression to /tests --- package.json | 2 +- .../vk-regression/plain-constraint-system.ts | 0 tests/vk-regression/tsconfig.json | 12 ++++++++++++ .../vk-regression/vk-regression.json | 0 .../vk-regression/vk-regression.ts | 14 +++++++------- 5 files changed, 20 insertions(+), 8 deletions(-) rename src/examples/primitive_constraint_system.ts => tests/vk-regression/plain-constraint-system.ts (100%) create mode 100644 tests/vk-regression/tsconfig.json rename src/examples/regression_test.json => tests/vk-regression/vk-regression.json (100%) rename src/examples/vk_regression.ts => tests/vk-regression/vk-regression.ts (86%) diff --git a/package.json b/package.json index 7fadc8b5f5..2587cce31a 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", "prepublishOnly": "npm run prepublish:web && npm run prepublish:node", - "dump-vks": "./run src/examples/vk_regression.ts --bundle --dump ./src/examples/regression_test.json", + "dump-vks": "./run tests/vk-regression/vk-regression.ts --bundle --dump", "format": "prettier --write --ignore-unknown **/*", "clean": "rimraf ./dist && rimraf ./src/bindings/compiled/_node_bindings", "clean-all": "npm run clean && rimraf ./tests/report && rimraf ./tests/test-artifacts", diff --git a/src/examples/primitive_constraint_system.ts b/tests/vk-regression/plain-constraint-system.ts similarity index 100% rename from src/examples/primitive_constraint_system.ts rename to tests/vk-regression/plain-constraint-system.ts diff --git a/tests/vk-regression/tsconfig.json b/tests/vk-regression/tsconfig.json new file mode 100644 index 0000000000..702d13e4a4 --- /dev/null +++ b/tests/vk-regression/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "include": ["."], + "exclude": [], + "compilerOptions": { + "rootDir": "../..", + "baseUrl": "../..", + "paths": { + "o1js": ["."] + } + } +} diff --git a/src/examples/regression_test.json b/tests/vk-regression/vk-regression.json similarity index 100% rename from src/examples/regression_test.json rename to tests/vk-regression/vk-regression.json diff --git a/src/examples/vk_regression.ts b/tests/vk-regression/vk-regression.ts similarity index 86% rename from src/examples/vk_regression.ts rename to tests/vk-regression/vk-regression.ts index a76b41aef4..26602d5f2e 100644 --- a/src/examples/vk_regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -1,14 +1,14 @@ import fs from 'fs'; -import { Voting_ } from './zkapps/voting/voting.js'; -import { Membership_ } from './zkapps/voting/membership.js'; -import { HelloWorld } from './zkapps/hello_world/hello_world.js'; -import { TokenContract, createDex } from './zkapps/dex/dex.js'; -import { GroupCS, BitwiseCS } from './primitive_constraint_system.js'; +import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; +import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; +import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; +import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; +import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; -// usage ./run ./src/examples/vk_regression.ts --bundle --dump ./src/examples/regression_test.json +// usage ./run ./tests/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]; @@ -40,7 +40,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ BitwiseCS, ]; -let filePath = jsonPath ? jsonPath : './src/examples/regression_test.json'; +let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; let RegressionJson: { [contractName: string]: { digest: string; From 0849ea98971339843569d9e743bdcf2f98a506a0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:31:53 +0100 Subject: [PATCH 0563/1215] tweak plain constraint system --- .../vk-regression/plain-constraint-system.ts | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index dd88343ad9..b4314f8a65 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -1,38 +1,8 @@ import { Field, Group, Gadgets, Provable, Scalar } from 'o1js'; -function mock(obj: { [K: string]: (...args: any) => void }, name: string) { - let methodKeys = Object.keys(obj); - - return { - analyzeMethods() { - let cs: Record< - string, - { - rows: number; - digest: string; - } - > = {}; - for (let key of methodKeys) { - let { rows, digest } = Provable.constraintSystem(obj[key]); - cs[key] = { - digest, - rows, - }; - } - - return cs; - }, - async compile() { - return { - verificationKey: { data: '', hash: '' }, - }; - }, - name, - digest: () => name, - }; -} +export { GroupCS, BitwiseCS }; -const GroupMock = { +const GroupCS = constraintSystem('Group Primitive', { add() { let g1 = Provable.witness(Group, () => Group.generator); let g2 = Provable.witness(Group, () => Group.generator); @@ -61,9 +31,9 @@ const GroupMock = { let g2 = Provable.witness(Group, () => Group.generator); g1.assertEquals(g2); }, -}; +}); -const BitwiseMock = { +const BitwiseCS = constraintSystem('Bitwise Primitive', { rot() { let a = Provable.witness(Field, () => new Field(12)); Gadgets.rotate(a, 2, 'left'); @@ -97,7 +67,40 @@ const BitwiseMock = { Gadgets.and(a, b, 48); Gadgets.and(a, b, 64); }, -}; +}); -export const GroupCS = mock(GroupMock, 'Group Primitive'); -export const BitwiseCS = mock(BitwiseMock, 'Bitwise Primitive'); +// mock ZkProgram API for testing + +function constraintSystem( + name: string, + obj: { [K: string]: (...args: any) => void } +) { + let methodKeys = Object.keys(obj); + + return { + analyzeMethods() { + let cs: Record< + string, + { + rows: number; + digest: string; + } + > = {}; + for (let key of methodKeys) { + let { rows, digest } = Provable.constraintSystem(obj[key]); + cs[key] = { + digest, + rows, + }; + } + return cs; + }, + async compile() { + return { + verificationKey: { data: '', hash: '' }, + }; + }, + name, + digest: () => name, + }; +} From 4c7f97e09e52a0dca4ea5c60da7946d849074066 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:34:34 +0100 Subject: [PATCH 0564/1215] move profiler --- src/examples/simple_zkapp.ts | 2 +- src/examples/{ => utils}/profiler.ts | 1 - src/examples/zkapps/composability.ts | 2 +- src/examples/zkapps/dex/happy-path-with-proofs.ts | 2 +- src/examples/zkapps/dex/run.ts | 2 +- src/examples/zkapps/dex/upgradability.ts | 2 +- src/examples/zkapps/hello_world/run.ts | 2 +- src/examples/zkapps/reducer/reducer_composite.ts | 2 +- src/examples/zkapps/voting/run.ts | 2 +- 9 files changed, 8 insertions(+), 9 deletions(-) rename src/examples/{ => utils}/profiler.ts (97%) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index d1bae21804..a58b2238cc 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -11,7 +11,7 @@ import { Bool, PublicKey, } from 'o1js'; -import { getProfiler } from './profiler.js'; +import { getProfiler } from './utils/profiler.js'; const doProofs = true; diff --git a/src/examples/profiler.ts b/src/examples/utils/profiler.ts similarity index 97% rename from src/examples/profiler.ts rename to src/examples/utils/profiler.ts index 9a93e417ee..c7f1e6c5a5 100644 --- a/src/examples/profiler.ts +++ b/src/examples/utils/profiler.ts @@ -1,4 +1,3 @@ -import { time } from 'console'; import fs from 'fs'; export { getProfiler }; diff --git a/src/examples/zkapps/composability.ts b/src/examples/zkapps/composability.ts index 54870bc2d1..253a992224 100644 --- a/src/examples/zkapps/composability.ts +++ b/src/examples/zkapps/composability.ts @@ -12,7 +12,7 @@ import { state, State, } from 'o1js'; -import { getProfiler } from '../profiler.js'; +import { getProfiler } from '../utils/profiler.js'; const doProofs = true; diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 9e78f3b7e8..f58f478fcf 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -2,7 +2,7 @@ import { isReady, Mina, AccountUpdate, UInt64 } from 'o1js'; import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; import { tic, toc } from '../../utils/tic-toc.node.js'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; await isReady; diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index 5fe5471d37..4899e3fdb2 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -10,7 +10,7 @@ import { import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; await isReady; let proofsEnabled = false; diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index af1eeeca22..02f4f2e78d 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -8,7 +8,7 @@ import { } from 'o1js'; import { createDex, TokenContract, addresses, keys, tokenIds } from './dex.js'; import { expect } from 'expect'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; await isReady; diff --git a/src/examples/zkapps/hello_world/run.ts b/src/examples/zkapps/hello_world/run.ts index 7371795ead..41a73dbb4d 100644 --- a/src/examples/zkapps/hello_world/run.ts +++ b/src/examples/zkapps/hello_world/run.ts @@ -1,5 +1,5 @@ import { AccountUpdate, Field, Mina, PrivateKey } from 'o1js'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; import { HelloWorld, adminPrivateKey } from './hello_world.js'; const HelloWorldProfier = getProfiler('Hello World'); diff --git a/src/examples/zkapps/reducer/reducer_composite.ts b/src/examples/zkapps/reducer/reducer_composite.ts index b9df1b0728..ce20fff86c 100644 --- a/src/examples/zkapps/reducer/reducer_composite.ts +++ b/src/examples/zkapps/reducer/reducer_composite.ts @@ -14,7 +14,7 @@ import { Provable, } from 'o1js'; import assert from 'node:assert/strict'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; await isReady; diff --git a/src/examples/zkapps/voting/run.ts b/src/examples/zkapps/voting/run.ts index e0e2c9836a..b2a3a9ec65 100644 --- a/src/examples/zkapps/voting/run.ts +++ b/src/examples/zkapps/voting/run.ts @@ -8,7 +8,7 @@ import { import { OffchainStorage } from './off_chain_storage.js'; import { Member } from './member.js'; import { testSet } from './test.js'; -import { getProfiler } from '../../profiler.js'; +import { getProfiler } from '../../utils/profiler.js'; console.log('Running Voting script...'); From fcab512ffe3d01162938ad3ec2cde09d9e9cf3ab Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:38:51 +0100 Subject: [PATCH 0565/1215] delete a few low value examples --- src/examples/ex01_small_preimage.ts | 32 ----------- src/examples/ex02_root_program.ts | 35 ------------ src/examples/print_constraint_system.ts | 23 -------- src/examples/schnorr_sign.ts | 72 ------------------------- 4 files changed, 162 deletions(-) delete mode 100644 src/examples/ex01_small_preimage.ts delete mode 100644 src/examples/ex02_root_program.ts delete mode 100644 src/examples/print_constraint_system.ts delete mode 100644 src/examples/schnorr_sign.ts diff --git a/src/examples/ex01_small_preimage.ts b/src/examples/ex01_small_preimage.ts deleted file mode 100644 index cfdbd18ce4..0000000000 --- a/src/examples/ex01_small_preimage.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'o1js'; - -await isReady; - -/* Exercise 1: - -Public input: a hash value h -Prove: - I know a value x < 2^32 such that hash(x) = h -*/ - -class Main extends Circuit { - @circuitMain - static main(preimage: Field, @public_ hash: Field) { - preimage.toBits(32); - Poseidon.hash([preimage]).assertEquals(hash); - } -} - -const kp = await Main.generateKeypair(); - -const preimage = Field.fromBits(Field.random().toBits().slice(0, 32)); -const hash = Poseidon.hash([preimage]); -const pi = await Main.prove([preimage], [hash], kp); -console.log('proof', pi); diff --git a/src/examples/ex02_root_program.ts b/src/examples/ex02_root_program.ts deleted file mode 100644 index 1127c1d81f..0000000000 --- a/src/examples/ex02_root_program.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Field, UInt64, Gadgets, ZkProgram } from 'o1js'; - -const Main = ZkProgram({ - name: 'example-with-custom-gates', - publicInput: Field, - methods: { - main: { - privateInputs: [UInt64], - method(y: Field, x: UInt64) { - Gadgets.rangeCheck64(x.value); - let y3 = y.square().mul(y); - y3.assertEquals(x.value); - }, - }, - }, -}); - -console.log('generating keypair...'); -console.time('generating keypair...'); -const kp = await Main.compile(); -console.timeEnd('generating keypair...'); - -console.log('prove...'); -console.time('prove...'); -const x = UInt64.from(8); -const y = new Field(2); -const proof = await Main.main(y, x); -console.timeEnd('prove...'); - -console.log('verify...'); -console.time('verify...'); -let ok = await Main.verify(proof); -console.timeEnd('verify...'); - -console.log('ok?', ok); diff --git a/src/examples/print_constraint_system.ts b/src/examples/print_constraint_system.ts deleted file mode 100644 index 6a6064a4b2..0000000000 --- a/src/examples/print_constraint_system.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'o1js'; - -class Main extends Circuit { - @circuitMain - static main(preimage: Field, @public_ hash: Field) { - Poseidon.hash([preimage]).assertEquals(hash); - } -} - -await isReady; - -console.log('generating keypair...'); -let kp = await Main.generateKeypair(); - -let cs = kp.constraintSystem(); -console.dir(cs, { depth: Infinity }); diff --git a/src/examples/schnorr_sign.ts b/src/examples/schnorr_sign.ts deleted file mode 100644 index 5739108f10..0000000000 --- a/src/examples/schnorr_sign.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Field, - Scalar, - Group, - Circuit, - public_, - circuitMain, - prop, - CircuitValue, - Signature, - isReady, -} from 'o1js'; - -await isReady; - -class Witness extends CircuitValue { - @prop signature: Signature; - @prop acc: Group; - @prop r: Scalar; -} - -// Public input: -// [newAcc: curve_point] -// Prove: -// I know [prevAcc: curve_point] and [signature : Signature] such that -// the signature verifies against Brave's public key and [newAcc] is -// a re-randomization of [prevAcc] - -export class Main extends Circuit { - @circuitMain - static main(w: Witness, @public_ newAcc: Group) { - let H = new Group({ x: -1, y: 2 }); - let r: Scalar = Provable.witness(Scalar, () => w.r); - let mask = H.scale(r); - let prevAcc: Group = Provable.witness(Group, () => w.acc); - let pubKey = Group.generator; // TODO: some literal group element - let signature = Provable.witness(Signature, () => w.signature); - //signature.verify(pubKey, [prevAcc.x, prevAcc.y, signature.r]).assertEquals(true); - prevAcc.add(mask).assertEquals(newAcc); - } -} - -class Circ extends Circuit { - @circuitMain - static main(@public_ x: Field) { - let acc = x; - for (let i = 0; i < 1000; ++i) { - acc = acc.mul(acc); - } - } -} - -function testSigning() { - const _msg = [Field.random()]; - const privKey = Scalar.random(); - const _pubKey = Group.generator.scale(privKey); - //const s = Signature.create(privKey, msg); - //console.log('signing worked', s.verify(pubKey, msg).toBoolean()); -} - -export function main() { - const before = new Date(); - const kp = Circ.generateKeypair(); - const after = new Date(); - testSigning(); - console.log('keypairgen', after.getTime() - before.getTime()); - console.log('random', Field.random()); - const proof = Circ.prove([], [new Field(2)], kp); - console.log(proof, kp); - let ok = Circ.verify([Field(2)], kp.verificationKey(), proof); - console.log('verified', ok); -} From 294725764b643c735b55b579d90b7ce483c122bc Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:43:56 +0100 Subject: [PATCH 0566/1215] folder for circuit examples --- src/examples/circuit/README.md | 7 +++++++ src/examples/{ => circuit}/ex00_preimage.ts | 0 src/examples/{ => circuit}/ex02_root.ts | 0 3 files changed, 7 insertions(+) create mode 100644 src/examples/circuit/README.md rename src/examples/{ => circuit}/ex00_preimage.ts (100%) rename src/examples/{ => circuit}/ex02_root.ts (100%) diff --git a/src/examples/circuit/README.md b/src/examples/circuit/README.md new file mode 100644 index 0000000000..1eecf13b11 --- /dev/null +++ b/src/examples/circuit/README.md @@ -0,0 +1,7 @@ +# `Circuit` examples + +These examples show how to use `Circuit`, which is a simple API to write a single circuit and create proofs for it. + +In contrast to `ZkProgram`, `Circuit` does not pass through Pickles, but creates a proof with Kimchi directly. Therefore, it does not support recursion, but is also much faster. + +Note that `Circuit` proofs are not compatible with Mina zkApps. diff --git a/src/examples/ex00_preimage.ts b/src/examples/circuit/ex00_preimage.ts similarity index 100% rename from src/examples/ex00_preimage.ts rename to src/examples/circuit/ex00_preimage.ts diff --git a/src/examples/ex02_root.ts b/src/examples/circuit/ex02_root.ts similarity index 100% rename from src/examples/ex02_root.ts rename to src/examples/circuit/ex02_root.ts From 9ca01c11c75aaa20375caf553f9f5006259fad8b Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:54:11 +0100 Subject: [PATCH 0567/1215] clean up circuit examples --- src/examples/circuit/ex00_preimage.ts | 26 ++++++++------------------ src/examples/circuit/ex02_root.ts | 27 +++++++++++++-------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/examples/circuit/ex00_preimage.ts b/src/examples/circuit/ex00_preimage.ts index 8f225429a8..22b3099225 100644 --- a/src/examples/circuit/ex00_preimage.ts +++ b/src/examples/circuit/ex00_preimage.ts @@ -1,19 +1,11 @@ -import { - Poseidon, - Field, - Circuit, - circuitMain, - public_, - isReady, -} from 'o1js'; - -/* Exercise 0: - -Public input: a hash value h -Prove: - I know a value x such that hash(x) = h -*/ - +import { Poseidon, Field, Circuit, circuitMain, public_ } from 'o1js'; + +/** + * Public input: a hash value h + * + * Prove: + * I know a value x such that hash(x) = h + */ class Main extends Circuit { @circuitMain static main(preimage: Field, @public_ hash: Field) { @@ -21,8 +13,6 @@ class Main extends Circuit { } } -await isReady; - console.log('generating keypair...'); const kp = await Main.generateKeypair(); diff --git a/src/examples/circuit/ex02_root.ts b/src/examples/circuit/ex02_root.ts index 54e5f0841b..838863e1ec 100644 --- a/src/examples/circuit/ex02_root.ts +++ b/src/examples/circuit/ex02_root.ts @@ -1,18 +1,17 @@ import { Field, Circuit, circuitMain, public_, UInt64, Gadgets } from 'o1js'; -/* Exercise 2: - -Public input: a field element x -Prove: - I know a value y that is a cube root of x. -*/ - +/** + * Public input: a field element x + * + * Prove: + * I know a value y < 2^64 that is a cube root of x. + */ class Main extends Circuit { @circuitMain - static main(@public_ y: Field, x: UInt64) { - Gadgets.rangeCheck64(x.value); + static main(@public_ x: Field, y: Field) { + Gadgets.rangeCheck64(y); let y3 = y.square().mul(y); - y3.assertEquals(x.value); + y3.assertEquals(x); } } @@ -23,15 +22,15 @@ console.timeEnd('generating keypair...'); console.log('prove...'); console.time('prove...'); -const x = UInt64.from(8); -const y = new Field(2); -const proof = await Main.prove([x], [y], kp); +const x = Field(8); +const y = Field(2); +const proof = await Main.prove([y], [x], kp); console.timeEnd('prove...'); console.log('verify...'); console.time('verify...'); let vk = kp.verificationKey(); -let ok = await Main.verify([y], vk, proof); +let ok = await Main.verify([x], vk, proof); console.timeEnd('verify...'); console.log('ok?', ok); From 68aab748138941f929e582455cd2bd63c9542488 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:54:50 +0100 Subject: [PATCH 0568/1215] rename circuit examples --- src/examples/circuit/{ex00_preimage.ts => preimage.ts} | 0 src/examples/circuit/{ex02_root.ts => root.ts} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/examples/circuit/{ex00_preimage.ts => preimage.ts} (100%) rename src/examples/circuit/{ex02_root.ts => root.ts} (91%) diff --git a/src/examples/circuit/ex00_preimage.ts b/src/examples/circuit/preimage.ts similarity index 100% rename from src/examples/circuit/ex00_preimage.ts rename to src/examples/circuit/preimage.ts diff --git a/src/examples/circuit/ex02_root.ts b/src/examples/circuit/root.ts similarity index 91% rename from src/examples/circuit/ex02_root.ts rename to src/examples/circuit/root.ts index 838863e1ec..15ab297ee4 100644 --- a/src/examples/circuit/ex02_root.ts +++ b/src/examples/circuit/root.ts @@ -1,4 +1,4 @@ -import { Field, Circuit, circuitMain, public_, UInt64, Gadgets } from 'o1js'; +import { Field, Circuit, circuitMain, public_, Gadgets } from 'o1js'; /** * Public input: a field element x From 0a09800ec4767f8e3cb375fed19cd2ad77ded9b8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 16:57:02 +0100 Subject: [PATCH 0569/1215] fixup ci --- run-ci-tests.sh | 2 +- tests/vk-regression/vk-regression.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run-ci-tests.sh b/run-ci-tests.sh index 246b93f769..cfcdf2e4c0 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -42,7 +42,7 @@ case $TEST_TYPE in "Verification Key Regression Check") echo "Running Regression checks" - ./run ./src/examples/vk_regression.ts --bundle + ./run ./tests/vk-regression/vk-regression.ts --bundle ;; "CommonJS test") diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 26602d5f2e..28a95542bd 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -57,7 +57,7 @@ try { } catch (error) { if (!dump) { throw Error( - `The requested file ${filePath} does not yet exist, try dumping the verification keys first. ./run ./src/examples/vk_regression.ts [--bundle] --dump ` + `The requested file ${filePath} does not yet exist, try dumping the verification keys first. npm run dump-vks` ); } } From 1a7c7105b4316be50fea69baeb80ff83e987e22a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 17:11:36 +0100 Subject: [PATCH 0570/1215] add cache type --- src/lib/proof_system.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 377daf1a82..3cd395d76d 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -260,7 +260,7 @@ function ZkProgram< } ): { name: string; - compile: () => Promise<{ verificationKey: string }>; + compile: (options?: { cache: Cache }) => Promise<{ verificationKey: string }>; verify: ( proof: Proof< InferProvableOrUndefined>, From d30e778906c6bf472159cab00d2decf92cb02e08 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 17:13:17 +0100 Subject: [PATCH 0571/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 49c3f0f309..af48d4e80f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 49c3f0f309c748f137a2c77d279a3a5e2b1918d6 +Subproject commit af48d4e80f51c2dff284bb7da23d91fb3f8fd3e9 From 0efb0f7ffb566cfb321a761e7e74c8b047c85987 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 3 Nov 2023 17:26:12 +0100 Subject: [PATCH 0572/1215] move zkprogram examples --- src/examples/zkprogram/README.md | 3 +++ src/examples/{ => zkprogram}/gadgets.ts | 0 src/examples/{ => zkprogram}/program-with-input.ts | 0 src/examples/{ => zkprogram}/program.ts | 0 4 files changed, 3 insertions(+) create mode 100644 src/examples/zkprogram/README.md rename src/examples/{ => zkprogram}/gadgets.ts (100%) rename src/examples/{ => zkprogram}/program-with-input.ts (100%) rename src/examples/{ => zkprogram}/program.ts (100%) diff --git a/src/examples/zkprogram/README.md b/src/examples/zkprogram/README.md new file mode 100644 index 0000000000..5386fe855f --- /dev/null +++ b/src/examples/zkprogram/README.md @@ -0,0 +1,3 @@ +# ZkProgram + +These examples focus on how to use `ZkProgram`, our main API for creating proofs outside of smart contract. diff --git a/src/examples/gadgets.ts b/src/examples/zkprogram/gadgets.ts similarity index 100% rename from src/examples/gadgets.ts rename to src/examples/zkprogram/gadgets.ts diff --git a/src/examples/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts similarity index 100% rename from src/examples/program-with-input.ts rename to src/examples/zkprogram/program-with-input.ts diff --git a/src/examples/program.ts b/src/examples/zkprogram/program.ts similarity index 100% rename from src/examples/program.ts rename to src/examples/zkprogram/program.ts From e45c853a9bf7c8c209a5a5b37a260104aacabe9b Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 11:06:31 -0700 Subject: [PATCH 0573/1215] fix(bitwise.unit-test.ts): update the condition to check if x or y is greater than or equal to 2^254 instead of 2^255 to ensure it fits into 255 bits --- src/lib/gadgets/bitwise.unit-test.ts | 2 +- src/lib/gadgets/gadgets.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index b7332484d0..face64fe2a 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -112,7 +112,7 @@ await equivalentAsync( { runs: 3 } )( (x, y) => { - if (x >= 2n ** 255n || y >= 2n ** 255n) + if (x >= 2n ** 254n || y >= 2n ** 254n) throw Error('Does not fit into 255 bits'); return x ^ y; }, diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index fab86d5e88..662c93e9ae 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -250,12 +250,11 @@ const Gadgets = { * For example, with `length = 2` (`paddedLength = 16`), `and()` will fail for any input that is larger than `2**16`. * * @example - * @example * ```typescript * let a = Field(3); // ... 000011 * let b = Field(5); // ... 000101 * - * let c = Gadgets.Gadgets.and(a, b, 2); // ... 000001 + * let c = Gadgets.and(a, b, 2); // ... 000001 * c.assertEquals(1); * ``` */ From 8c63420418188cd1c0ebee9b3d59899c14afeb43 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 11:10:10 -0700 Subject: [PATCH 0574/1215] chore(regression_test.json): dump vks --- src/examples/regression_test.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index c623b703ab..d20625d730 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -182,15 +182,7 @@ }, "notChecked": { "rows": 17, - "digest": "5e01b2cad70489c7bec1546b84ac868d" - }, - "leftShift": { - "rows": 7, - "digest": "66de39ad3dd5807f760341ec85a6cc41" - }, - "rightShift": { - "rows": 7, - "digest": "a32264f2d4c3092f30d600fa9506385b" + "digest": "db50dc3bd32f466adc1f4ae6de37a4d7" }, "leftShift": { "rows": 7, From b9f4b25ebcee788e99dc961615637bf40c800121 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 11:19:03 -0700 Subject: [PATCH 0575/1215] chore(bindings): update subproject commit hash from 22af158a9bd66b1b74376b1a36517722e931fcf6 to 1e7512296a2cf1653277cc9b11482975156fb5c9 --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 22af158a9b..1e7512296a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 22af158a9bd66b1b74376b1a36517722e931fcf6 +Subproject commit 1e7512296a2cf1653277cc9b11482975156fb5c9 From 38d6d458d1c6dce35a50da0fedc767786bc3e682 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 11:40:13 -0700 Subject: [PATCH 0576/1215] fix(regression_test.json): update digest value for "notChecked" test case to reflect the correct value after regression testing --- src/examples/regression_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/regression_test.json b/src/examples/regression_test.json index d20625d730..b702555394 100644 --- a/src/examples/regression_test.json +++ b/src/examples/regression_test.json @@ -182,7 +182,7 @@ }, "notChecked": { "rows": 17, - "digest": "db50dc3bd32f466adc1f4ae6de37a4d7" + "digest": "5e01b2cad70489c7bec1546b84ac868d" }, "leftShift": { "rows": 7, From a29a68023abe9fdd7099a3c1a9dd8bf676489713 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 12:47:26 -0700 Subject: [PATCH 0577/1215] feat(bitwise.unit-test.ts): add equivalent async tests for notChecked and notUnchecked functions --- src/lib/gadgets/bitwise.unit-test.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index face64fe2a..d83cc5d563 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -112,8 +112,9 @@ await equivalentAsync( { runs: 3 } )( (x, y) => { - if (x >= 2n ** 254n || y >= 2n ** 254n) + if (x >= Fp.modulus || y >= Fp.modulus) { throw Error('Does not fit into 255 bits'); + } return x ^ y; }, async (x, y) => { @@ -122,6 +123,30 @@ await equivalentAsync( } ); +// notChecked +await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( + (x) => { + if (x > Fp.modulus) throw Error('Does not fit into 255 bits'); + return Fp.not(x, 254); + }, + async (x) => { + let proof = await Bitwise.notChecked(x); + return proof.publicOutput; + } +); + +// notUnchecked +await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( + (x) => { + if (x > Fp.modulus) throw Error('Does not fit into 255 bits'); + return Fp.not(x, 254); + }, + async (x) => { + let proof = await Bitwise.notUnchecked(x); + return proof.publicOutput; + } +); + await equivalentAsync( { from: [maybeUint64, maybeUint64], to: field }, { runs: 3 } From 0524ebdfd5e769e3843533b5a59b062bc35de4d5 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 13:00:35 -0700 Subject: [PATCH 0578/1215] fix(bitwise.unit-test.ts): update error message to include the value of Fp.modulus for better error reporting and debugging --- src/lib/gadgets/bitwise.unit-test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index d83cc5d563..ea159f93fa 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -113,7 +113,7 @@ await equivalentAsync( )( (x, y) => { if (x >= Fp.modulus || y >= Fp.modulus) { - throw Error('Does not fit into 255 bits'); + throw Error(`Does not fit into ${Fp.modulus}`); } return x ^ y; }, @@ -126,7 +126,7 @@ await equivalentAsync( // notChecked await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error('Does not fit into 255 bits'); + if (x > Fp.modulus) throw Error(`Does not fit into ${Fp.modulus}`); return Fp.not(x, 254); }, async (x) => { @@ -138,7 +138,7 @@ await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( // notUnchecked await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error('Does not fit into 255 bits'); + if (x > Fp.modulus) throw Error(`Does not fit into ${Fp.modulus}`); return Fp.not(x, 254); }, async (x) => { From aaa5af0aff2fc26645850833fde6de3d4fc7e680 Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 17:35:34 -0700 Subject: [PATCH 0579/1215] fix(gadgets.ts): update documentation to clarify that the operation will fail if the input value is larger than 254 bits --- src/lib/gadgets/gadgets.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f92e01e25e..5a1f00d52d 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -129,7 +129,7 @@ const Gadgets = { * * The `length` parameter lets you define how many bits to NOT. * - * **Note:** Specifying a larger `length` parameter adds additional constraints. The operation will fail if the length is larger than 254. + * **Note:** Specifying a larger `length` parameter adds additional constraints. The operation will fail if the length or the input value is larger than 254. * * NOT is implemented in two different ways. If the `checked` parameter is set to `true` * the {@link Gadgets.xor} gadget is reused with a second argument to be an @@ -155,12 +155,13 @@ const Gadgets = { * b.assertEquals(0b1010); * ``` * - * @param a - The value to apply NOT to. + * @param a - The value to apply NOT to. The operation will fail if the value is larger than 254. * @param length - The number of bits to be considered for the NOT operation. * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. * + * @throws Throws an error if the input value exceeds 254 bits. */ not(a: Field, length: number, checked: boolean = false) { return not(a, length, checked); From e1d3a7cc75c19bbe292a2e9eb077f8dec870405e Mon Sep 17 00:00:00 2001 From: ymekuria Date: Fri, 3 Nov 2023 20:37:23 -0700 Subject: [PATCH 0580/1215] fix(bitwise.unit-test.ts): update tests --- src/lib/gadgets/bitwise.unit-test.ts | 35 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index ea159f93fa..e07bcd4b81 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,6 +20,16 @@ let maybeUint64: Spec = { let uint = (length: number) => fieldWithRng(Random.biguint(length)); +let notTestInput: Spec = { + ...field, + rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => { + if (mod(x, Field.ORDER).toString() > '254') { + return mod(randomBigInt(254), Field.ORDER); + } + return mod(x, Field.ORDER); + }), +}; + let Bitwise = ZkProgram({ name: 'bitwise', publicOutput: Field, @@ -123,26 +133,25 @@ await equivalentAsync( } ); -// notChecked -await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( +await equivalentAsync({ from: [notTestInput], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error(`Does not fit into ${Fp.modulus}`); + if (x > Fp.modulus) throw Error(`Does not fit into 254`); return Fp.not(x, 254); }, async (x) => { - let proof = await Bitwise.notChecked(x); + console.log('x notUnChecked async', x); + let proof = await Bitwise.notUnchecked(x); return proof.publicOutput; } ); - -// notUnchecked -await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( +await equivalentAsync({ from: [notTestInput], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error(`Does not fit into ${Fp.modulus}`); + console.log('x notChecked bigint'); + if (x > Fp.modulus) throw Error(`Does not fit into 254`); return Fp.not(x, 254); }, async (x) => { - let proof = await Bitwise.notUnchecked(x); + let proof = await Bitwise.notChecked(x); return proof.publicOutput; } ); @@ -194,3 +203,11 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return proof.publicOutput; } ); + +function randomBigInt(bits: number) { + let randomString = '1'; + for (let i = 1; i < bits; i++) { + randomString += Math.floor(Math.random() * 2).toString(); + } + return BigInt('0b' + randomString); +} From e960b3ab79aa14a44eaa7ff2de5b43916770f3fb Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Nov 2023 16:15:05 +0300 Subject: [PATCH 0581/1215] fix xor length and fix tests --- src/lib/gadgets/bitwise.ts | 7 ++----- src/lib/gadgets/bitwise.unit-test.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 0943eec543..c2082b7172 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -56,11 +56,8 @@ function xor(a: Field, b: Field, length: number) { // check that both input lengths are positive assert(length > 0, `Input lengths need to be positive values.`); - // check that length does not exceed maximum field size in bits - assert( - length <= Field.sizeInBits(), - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` - ); + // check that length does not exceed maximum 254 size in bits + assert(length <= 254, `Length ${length} exceeds maximum of 254 bits.`); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table let padLength = Math.ceil(length / 16) * 16; diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index e07bcd4b81..90053d3137 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -37,7 +37,7 @@ let Bitwise = ZkProgram({ xor: { privateInputs: [Field, Field], method(a: Field, b: Field) { - return Gadgets.xor(a, b, 255); + return Gadgets.xor(a, b, 254); }, }, notUnchecked: { From 702b30935e6251720bb7f8c6a04366f098720036 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Nov 2023 18:58:48 +0300 Subject: [PATCH 0582/1215] cleanup tests --- src/lib/gadgets/bitwise.unit-test.ts | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 90053d3137..4b3e3c74f0 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -20,16 +20,6 @@ let maybeUint64: Spec = { let uint = (length: number) => fieldWithRng(Random.biguint(length)); -let notTestInput: Spec = { - ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => { - if (mod(x, Field.ORDER).toString() > '254') { - return mod(randomBigInt(254), Field.ORDER); - } - return mod(x, Field.ORDER); - }), -}; - let Bitwise = ZkProgram({ name: 'bitwise', publicOutput: Field, @@ -133,20 +123,18 @@ await equivalentAsync( } ); -await equivalentAsync({ from: [notTestInput], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( (x) => { if (x > Fp.modulus) throw Error(`Does not fit into 254`); return Fp.not(x, 254); }, async (x) => { - console.log('x notUnChecked async', x); let proof = await Bitwise.notUnchecked(x); return proof.publicOutput; } ); -await equivalentAsync({ from: [notTestInput], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( (x) => { - console.log('x notChecked bigint'); if (x > Fp.modulus) throw Error(`Does not fit into 254`); return Fp.not(x, 254); }, @@ -203,11 +191,3 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return proof.publicOutput; } ); - -function randomBigInt(bits: number) { - let randomString = '1'; - for (let i = 1; i < bits; i++) { - randomString += Math.floor(Math.random() * 2).toString(); - } - return BigInt('0b' + randomString); -} From 4e7968ca91fc625251cd4a2c3c74ee838ab469b0 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Nov 2023 19:19:25 +0300 Subject: [PATCH 0583/1215] cleanup tests --- src/lib/gadgets/bitwise.unit-test.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 4b3e3c74f0..18dd6bdca5 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -1,6 +1,5 @@ import { ZkProgram } from '../proof_system.js'; import { - Spec, equivalent, equivalentAsync, field, @@ -11,9 +10,9 @@ import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; -let maybeUint64: Spec = { +const maybeField = { ...field, - rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) => + rng: Random.map(Random.oneOf(Random.field, Random.field.invalid), (x) => mod(x, Field.ORDER) ), }; @@ -108,12 +107,12 @@ await Bitwise.compile(); }); await equivalentAsync( - { from: [maybeUint64, maybeUint64], to: field }, + { from: [maybeField, maybeField], to: field }, { runs: 3 } )( (x, y) => { - if (x >= Fp.modulus || y >= Fp.modulus) { - throw Error(`Does not fit into ${Fp.modulus}`); + if (x > 2n ** 254n || y > 2n ** 254n) { + throw Error(`Does not fit into 254 bits`); } return x ^ y; }, @@ -123,9 +122,9 @@ await equivalentAsync( } ); -await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error(`Does not fit into 254`); + if (x > 2n ** 254n) throw Error(`Does not fit into 254 bits`); return Fp.not(x, 254); }, async (x) => { @@ -133,9 +132,9 @@ await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( return proof.publicOutput; } ); -await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( (x) => { - if (x > Fp.modulus) throw Error(`Does not fit into 254`); + if (x > 2n ** 254n) throw Error(`Does not fit into 254 bits`); return Fp.not(x, 254); }, async (x) => { @@ -145,7 +144,7 @@ await equivalentAsync({ from: [maybeUint64], to: field }, { runs: 3 })( ); await equivalentAsync( - { from: [maybeUint64, maybeUint64], to: field }, + { from: [maybeField, maybeField], to: field }, { runs: 3 } )( (x, y) => { From 9a4094552428824159e53c5309cde7bc87873eec Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Nov 2023 22:40:03 +0300 Subject: [PATCH 0584/1215] fix tests, bump submodule --- src/bindings | 2 +- src/lib/gadgets/bitwise.unit-test.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/bindings b/src/bindings index 1e7512296a..c5aa5f163b 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1e7512296a2cf1653277cc9b11482975156fb5c9 +Subproject commit c5aa5f163bf9e86adea3b85cdb75d84e56b9d461 diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 18dd6bdca5..b8a0533b42 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -111,7 +111,7 @@ await equivalentAsync( { runs: 3 } )( (x, y) => { - if (x > 2n ** 254n || y > 2n ** 254n) { + if (x >= 2n ** 254n || y >= 2n ** 254n) { throw Error(`Does not fit into 254 bits`); } return x ^ y; @@ -124,7 +124,6 @@ await equivalentAsync( await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( (x) => { - if (x > 2n ** 254n) throw Error(`Does not fit into 254 bits`); return Fp.not(x, 254); }, async (x) => { @@ -134,7 +133,7 @@ await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( ); await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( (x) => { - if (x > 2n ** 254n) throw Error(`Does not fit into 254 bits`); + if (x > 2n ** 254n) throw Error('Does not fit into 254 bit'); return Fp.not(x, 254); }, async (x) => { From 68085dece655ec886bc9b610775cdb2dd49bc1a2 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Nov 2023 22:59:25 +0300 Subject: [PATCH 0585/1215] fix xor --- src/lib/gadgets/bitwise.unit-test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index b8a0533b42..277fc17efc 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -106,14 +106,8 @@ await Bitwise.compile(); ); }); -await equivalentAsync( - { from: [maybeField, maybeField], to: field }, - { runs: 3 } -)( +await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs: 3 })( (x, y) => { - if (x >= 2n ** 254n || y >= 2n ** 254n) { - throw Error(`Does not fit into 254 bits`); - } return x ^ y; }, async (x, y) => { From 85cb85e710bd01137ece0567c98488c0788c4e49 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 8 Nov 2023 18:11:23 +0300 Subject: [PATCH 0586/1215] release o1js with new bitwise gadgets, v0.14.1 --- CHANGELOG.md | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 296677caa1..0931d3239d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/e8e7510e1...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) + +## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) ### Added diff --git a/package.json b/package.json index 2587cce31a..8e748d808f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.14.0", + "version": "0.14.1", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From cbf013b471c7e555398fb225c38642c2d9733692 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 8 Nov 2023 18:17:08 +0300 Subject: [PATCH 0587/1215] regenerate package-lock --- package-lock.json | 6104 +-------------------------------------------- 1 file changed, 3 insertions(+), 6101 deletions(-) diff --git a/package-lock.json b/package-lock.json index 640490462f..93ea97ca43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.14.0", - "lockfileVersion": 2, + "version": "0.14.1", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.14.0", + "version": "0.14.1", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", @@ -539,54 +539,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", - "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", - "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", - "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", @@ -603,294 +555,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", - "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", - "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", - "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", - "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", - "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", - "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", - "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", - "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", - "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", - "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", - "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", - "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", - "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", - "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", - "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", - "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", - "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", - "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", @@ -7939,5767 +7603,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz", - "integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==", - "dev": true - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.18.13", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@esbuild/android-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", - "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", - "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", - "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", - "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", - "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", - "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", - "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", - "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", - "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", - "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", - "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", - "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", - "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", - "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", - "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", - "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", - "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", - "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", - "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", - "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", - "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", - "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", - "dev": true, - "optional": true - }, - "@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "dependencies": { - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - } - } - }, - "@jest/expect-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.1.tgz", - "integrity": "sha512-Tw5kUUOKmXGQDmQ9TSgTraFFS7HMC1HG/B7y0AN2G2UzjdAXz9BzK2rmNpCSDl7g7y0Gf/VLBm//blonvhtOTQ==", - "dev": true, - "requires": { - "jest-get-type": "^29.0.0" - }, - "dependencies": { - "jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true - } - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - } - }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@playwright/test": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.2.tgz", - "integrity": "sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==", - "dev": true, - "requires": { - "@types/node": "*", - "playwright-core": "1.25.2" - } - }, - "@sinclair/typebox": { - "version": "0.24.28", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", - "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/isomorphic-fetch": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", - "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "27.0.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.3.tgz", - "integrity": "sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg==", - "dev": true, - "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "18.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", - "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", - "dev": true - }, - "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", - "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", - "dev": true, - "peer": true, - "requires": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" - } - }, - "@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" - } - }, - "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-sequence-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", - "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==" - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001384", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz", - "integrity": "sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "detect-gpu": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.5.tgz", - "integrity": "sha512-gKBKx8mj0Fi/8DnjuzxU+aNYF1yWvPxtiOV9o72539wW0HP3ZTJVol/0FUasvdxIY1Bhi19SwIINqXGO+RB/sA==", - "requires": { - "webgl-constants": "^1.1.1" - } - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "electron-to-chromium": { - "version": "1.4.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz", - "integrity": "sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw==", - "dev": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esbuild": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", - "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.19.2", - "@esbuild/android-arm64": "0.19.2", - "@esbuild/android-x64": "0.19.2", - "@esbuild/darwin-arm64": "0.19.2", - "@esbuild/darwin-x64": "0.19.2", - "@esbuild/freebsd-arm64": "0.19.2", - "@esbuild/freebsd-x64": "0.19.2", - "@esbuild/linux-arm": "0.19.2", - "@esbuild/linux-arm64": "0.19.2", - "@esbuild/linux-ia32": "0.19.2", - "@esbuild/linux-loong64": "0.19.2", - "@esbuild/linux-mips64el": "0.19.2", - "@esbuild/linux-ppc64": "0.19.2", - "@esbuild/linux-riscv64": "0.19.2", - "@esbuild/linux-s390x": "0.19.2", - "@esbuild/linux-x64": "0.19.2", - "@esbuild/netbsd-x64": "0.19.2", - "@esbuild/openbsd-x64": "0.19.2", - "@esbuild/sunos-x64": "0.19.2", - "@esbuild/win32-arm64": "0.19.2", - "@esbuild/win32-ia32": "0.19.2", - "@esbuild/win32-x64": "0.19.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", - "dev": true - }, - "espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", - "dev": true, - "requires": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.1.tgz", - "integrity": "sha512-yQgemsjLU+1S8t2A7pXT3Sn/v5/37LY8J+tocWtKEA0iEYYc6gfKbbJJX2fxHZmd7K9WpdbQqXUpmYkq1aewYg==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.0.1", - "jest-get-type": "^29.0.0", - "jest-matcher-utils": "^29.0.1", - "jest-message-util": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", - "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.1.tgz", - "integrity": "sha512-l8PYeq2VhcdxG9tl5cU78ClAlg/N7RtVSp0v3MlXURR0Y99i6eFnegmasOandyTmO6uEdo20+FByAjBFEO9nuw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.0.0", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - } - }, - "jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true - }, - "jest-matcher-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.1.tgz", - "integrity": "sha512-/e6UbCDmprRQFnl7+uBKqn4G22c/OmwriE5KCMVqxhElKCQUDcFnq5XM9iJeKtzy4DUjxT27y9VHmKPD8BQPaw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.0.1", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - } - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "howslow": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/howslow/-/howslow-0.1.0.tgz", - "integrity": "sha512-AD1ERdUseZEi/XyLBa/9LNv4l4GvCCkNT76KpYp0YEv1up8Ow/ZzLy71OtlSdv6b39wxvzJKq9VZLH/83PrQkQ==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - } - }, - "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - } - }, - "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "dependencies": { - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "playwright-core": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.2.tgz", - "integrity": "sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "replace-in-file": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", - "integrity": "sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==", - "dev": true, - "requires": { - "chalk": "^4.1.2", - "glob": "^7.2.0", - "yargs": "^17.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shiki": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.1.tgz", - "integrity": "sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==", - "dev": true, - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typedoc": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", - "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", - "dev": true, - "requires": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.0", - "shiki": "^0.14.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "typedoc-plugin-markdown": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.3.tgz", - "integrity": "sha512-idntFYu3vfaY3eaD+w9DeRd0PmNGqGuNLKihPU9poxFGnATJYGn9dPtEhn2QrTdishFMg7jPXAhos+2T6YCWRQ==", - "dev": true, - "requires": { - "handlebars": "^4.7.7" - } - }, - "typedoc-plugin-merge-modules": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.0.1.tgz", - "integrity": "sha512-7fiMYDUaeslsGSFDevw+azhD0dFJce0h2g5UuQ8zXljoky+YfmzoNkoTCx+KWaNJo6rz2DzaD2feVJyUhvUegg==", - "dev": true, - "requires": {} - }, - "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webgl-constants": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", - "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } From ec42a36a3a54951df96058d463a05b1cc59a6284 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 8 Nov 2023 18:18:23 +0300 Subject: [PATCH 0588/1215] add no unreleased changes yet --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0931d3239d..e9ff71d070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) +> No unreleased changes yet + ## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) ### Added From b6bdf13b6df93c89d5d7cadd1623a191bb95d9a7 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 01:34:49 +0100 Subject: [PATCH 0589/1215] refactor common mul logic into function --- src/lib/gadgets/foreign-field.ts | 57 ++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index d9052def29..e3b15a6bc7 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -13,7 +13,7 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { ForeignField, Field3, Sign }; +export { ForeignField, Field3, bigint3, Sign }; type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; @@ -110,12 +110,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { } function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { - // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); - let f_ = (1n << L3) - f; - let [f_0, f_1, f_2] = split(f_); - let f2 = f >> L2; - let f2Bound = (1n << L) - f2 - 1n; // constant case if (a.every((x) => x.isConstant()) && b.every((x) => x.isConstant())) { @@ -125,6 +120,38 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { return [ForeignField.from(r), ForeignField.from(q)]; } + // provable case + let { + r: [r01, r2], + q, + q2Bound, + } = multiplyNoRangeCheck(a, b, f); + + // limb range checks on quotient and remainder + multiRangeCheck(...q); + let r = compactMultiRangeCheck(r01, r2); + + // range check on q and r bounds + // TODO: this uses one RC too many.. need global RC stack + let f2 = f >> L2; + let f2Bound = (1n << L) - f2 - 1n; + let [r2Bound, zero] = exists(2, () => [r2.toBigInt() + f2Bound, 0n]); + multiRangeCheck(q2Bound, r2Bound, zero); + + // constrain r2 bound, zero + r2.add(f2Bound).assertEquals(r2Bound); + zero.assertEquals(0); + + return [r, q]; +} + +function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { + // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md + let f_ = (1n << L3) - f; + let [f_0, f_1, f_2] = split(f_); + let f2 = f >> L2; + let f2Bound = (1n << L) - f2 - 1n; + let witnesses = exists(21, () => { // split inputs into 3 limbs let [a0, a1, a2] = bigint3(a); @@ -193,12 +220,13 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { ] = witnesses; let q: Field3 = [q0, q1, q2]; + let r: [Field, Field] = [r01, r2]; // ffmul gate. this already adds the following zero row. Gates.foreignFieldMul({ left: a, right: b, - remainder: [r01, r2], + remainder: r, quotient: q, quotientHiBound: q2Bound, product1: [p10, p110, p111], @@ -209,20 +237,7 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { negForeignFieldModulus: [f_0, f_1, f_2], }); - // limb range checks on quotient and remainder - multiRangeCheck(...q); - let r = compactMultiRangeCheck(r01, r2); - - // range check on q and r bounds - // TODO: this uses one RC too many.. need global RC stack - let [r2Bound, zero] = exists(2, () => [r2.toBigInt() + f2Bound, 0n]); - multiRangeCheck(q2Bound, r2Bound, zero); - - // constrain r2 bound, zero - r2.add(f2Bound).assertEquals(r2Bound); - zero.assertEquals(0); - - return [r, q]; + return { r, q, q2Bound }; } function Field3(x: bigint3): Field3 { From d0f592d863c29c4f566a164758c7c768fe13ca07 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 13:04:22 +0100 Subject: [PATCH 0590/1215] ff inverse --- src/lib/gadgets/common.ts | 2 +- src/lib/gadgets/foreign-field.ts | 50 +++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 6b97c6016b..680d87b19b 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -27,7 +27,7 @@ function exists TupleN>( return TupleN.fromArray(n, vars); } -function assert(stmt: boolean, message?: string) { +function assert(stmt: boolean, message?: string): asserts stmt { if (!stmt) { throw Error(message ?? 'Assertion failed'); } diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index e3b15a6bc7..a2b297268b 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -1,4 +1,7 @@ -import { mod } from '../../bindings/crypto/finite_field.js'; +import { + inverse as modInverse, + mod, +} from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; @@ -32,6 +35,9 @@ const ForeignField = { mul(x: Field3, y: Field3, f: bigint) { return multiply(x, y, f); }, + inv(x: Field3, f: bigint) { + return inverse(x, f); + }, // helper methods from(x: bigint): Field3 { @@ -121,11 +127,7 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { } // provable case - let { - r: [r01, r2], - q, - q2Bound, - } = multiplyNoRangeCheck(a, b, f); + let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(a, b, f); // limb range checks on quotient and remainder multiRangeCheck(...q); @@ -140,11 +142,40 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { // constrain r2 bound, zero r2.add(f2Bound).assertEquals(r2Bound); - zero.assertEquals(0); + zero.assertEquals(0n); return [r, q]; } +function inverse(x: Field3, f: bigint): Field3 { + // constant case + if (x.every((x) => x.isConstant())) { + let xInv = modInverse(ForeignField.toBigint(x), f); + assert(xInv !== undefined, 'inverse exists'); + return ForeignField.from(xInv); + } + + // provable case + let xInv = exists(3, () => { + let xInv = modInverse(ForeignField.toBigint(x), f); + return xInv === undefined ? [0n, 0n, 0n] : split(xInv); + }); + let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, xInv, f); + + // range checks on quotient + multiRangeCheck(...q); + // TODO: this uses one RC too many.. need global RC stack + let [zero] = exists(1, () => [0n]); + multiRangeCheck(q2Bound, zero, zero); + zero.assertEquals(0n); + + // assert r === 1 + r01.assertEquals(1n); + r2.assertEquals(0n); + + return xInv; +} + function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md let f_ = (1n << L3) - f; @@ -220,13 +251,12 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { ] = witnesses; let q: Field3 = [q0, q1, q2]; - let r: [Field, Field] = [r01, r2]; // ffmul gate. this already adds the following zero row. Gates.foreignFieldMul({ left: a, right: b, - remainder: r, + remainder: [r01, r2], quotient: q, quotientHiBound: q2Bound, product1: [p10, p110, p111], @@ -237,7 +267,7 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { negForeignFieldModulus: [f_0, f_1, f_2], }); - return { r, q, q2Bound }; + return { r01, r2, q, q2Bound }; } function Field3(x: bigint3): Field3 { From b403745b6381f0a772bb3711627527d484c2b23d Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 14:13:25 +0100 Subject: [PATCH 0591/1215] fix inverse --- src/lib/gadgets/foreign-field.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index a2b297268b..a85df1a330 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -162,11 +162,15 @@ function inverse(x: Field3, f: bigint): Field3 { }); let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, xInv, f); - // range checks on quotient + // limb range checks on quotient and inverse multiRangeCheck(...q); + multiRangeCheck(...xInv); + // range check on q and xInv bounds // TODO: this uses one RC too many.. need global RC stack - let [zero] = exists(1, () => [0n]); - multiRangeCheck(q2Bound, zero, zero); + let f2 = f >> L2; + let f2Bound = (1n << L) - f2 - 1n; + let [xInv2Bound, zero] = exists(2, () => [xInv[2].toBigInt() + f2Bound, 0n]); + multiRangeCheck(q2Bound, xInv2Bound, zero); zero.assertEquals(0n); // assert r === 1 From f7e2fe404272ddd547d45bfcb5f4ab6633c4ee9c Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 15:32:02 +0100 Subject: [PATCH 0592/1215] division --- src/lib/gadgets/common.ts | 26 +++++++++- src/lib/gadgets/foreign-field.ts | 85 +++++++++++++++++++++++++------- src/lib/gadgets/range-check.ts | 6 ++- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 680d87b19b..b497ef88d8 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,6 +1,6 @@ import { Provable } from '../provable.js'; -import { Field, FieldConst } from '../field.js'; -import { TupleN } from '../util/types.js'; +import { Field, FieldConst, FieldType } from '../field.js'; +import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; @@ -9,6 +9,9 @@ const MAX_BITS = 64 as const; export { MAX_BITS, exists, + toVars, + existsOne, + toVar, assert, bitSlice, witnessSlice, @@ -27,6 +30,25 @@ function exists TupleN>( return TupleN.fromArray(n, vars); } +function toVars>( + fields: T +): { [k in keyof T]: Field } { + return Tuple.map(fields, toVar); +} + +function existsOne(compute: () => bigint) { + let varMl = Snarky.existsVar(() => FieldConst.fromBigint(compute())); + return new Field(varMl); +} + +function toVar(x: Field | bigint) { + // don't change existing vars + if (x instanceof Field && x.value[1] === FieldType.Var) return x; + let xVar = existsOne(() => Field.from(x).toBigInt()); + xVar.assertEquals(x); + return xVar; +} + function assert(stmt: boolean, message?: string): asserts stmt { if (!stmt) { throw Error(message ?? 'Assertion failed'); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index a85df1a330..bc9212c0c2 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -4,8 +4,9 @@ import { } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; +import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; -import { assert, bitSlice, exists } from './common.js'; +import { assert, bitSlice, exists, existsOne, toVar } from './common.js'; import { L, lMask, @@ -38,6 +39,9 @@ const ForeignField = { inv(x: Field3, f: bigint) { return inverse(x, f); }, + div(x: Field3, y: Field3, f: bigint, { allowZeroOverZero = false } = {}) { + return divide(x, y, f, allowZeroOverZero); + }, // helper methods from(x: bigint): Field3 { @@ -115,15 +119,13 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return { result: [r0, r1, r2] satisfies Field3, overflow }; } -function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { +function multiply(a: Field3, b: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case if (a.every((x) => x.isConstant()) && b.every((x) => x.isConstant())) { let ab = ForeignField.toBigint(a) * ForeignField.toBigint(b); - let q = ab / f; - let r = ab - q * f; - return [ForeignField.from(r), ForeignField.from(q)]; + return ForeignField.from(mod(ab, f)); } // provable case @@ -135,19 +137,15 @@ function multiply(a: Field3, b: Field3, f: bigint): [Field3, Field3] { // range check on q and r bounds // TODO: this uses one RC too many.. need global RC stack - let f2 = f >> L2; - let f2Bound = (1n << L) - f2 - 1n; - let [r2Bound, zero] = exists(2, () => [r2.toBigInt() + f2Bound, 0n]); - multiRangeCheck(q2Bound, r2Bound, zero); - - // constrain r2 bound, zero - r2.add(f2Bound).assertEquals(r2Bound); - zero.assertEquals(0n); + let r2Bound = weakBound(r2, f); + multiRangeCheck(q2Bound, r2Bound, toVar(0n)); - return [r, q]; + return r; } function inverse(x: Field3, f: bigint): Field3 { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + // constant case if (x.every((x) => x.isConstant())) { let xInv = modInverse(ForeignField.toBigint(x), f); @@ -167,11 +165,9 @@ function inverse(x: Field3, f: bigint): Field3 { multiRangeCheck(...xInv); // range check on q and xInv bounds // TODO: this uses one RC too many.. need global RC stack - let f2 = f >> L2; - let f2Bound = (1n << L) - f2 - 1n; - let [xInv2Bound, zero] = exists(2, () => [xInv[2].toBigInt() + f2Bound, 0n]); - multiRangeCheck(q2Bound, xInv2Bound, zero); - zero.assertEquals(0n); + // TODO: make sure that we can just pass non-vars to multiRangeCheck() to get rid of this + let xInv2Bound = weakBound(xInv[2], f); + multiRangeCheck(q2Bound, xInv2Bound, new Field(0n)); // assert r === 1 r01.assertEquals(1n); @@ -180,6 +176,51 @@ function inverse(x: Field3, f: bigint): Field3 { return xInv; } +function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if (x.every((x) => x.isConstant()) && y.every((x) => x.isConstant())) { + let yInv = modInverse(ForeignField.toBigint(y), f); + assert(yInv !== undefined, 'inverse exists'); + return ForeignField.from(mod(ForeignField.toBigint(x) * yInv, f)); + } + + // provable case + // to show that z = x/y, we prove that z*y = x and y != 0 (the latter avoids the unconstrained 0/0 case) + let z = exists(3, () => { + let yInv = modInverse(ForeignField.toBigint(y), f); + if (yInv === undefined) return [0n, 0n, 0n]; + return split(mod(ForeignField.toBigint(x) * yInv, f)); + }); + let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(z, y, f); + + // limb range checks on quotient and result + multiRangeCheck(...q); + multiRangeCheck(...z); + // range check on q and result bounds + let z2Bound = weakBound(z[2], f); + multiRangeCheck(q2Bound, z2Bound, new Field(0n)); + + // check that r === y + // this means we don't have to range check r + let y01 = y[0].add(y[1].mul(1n << L)); + r01.assertEquals(y01); + r2.assertEquals(y[2]); + + if (!allowZeroOverZero) { + // assert that y != 0 mod f by checking that it doesn't equal 0 or f + // this works because we assume y[2] <= f2 + // TODO is this the most efficient way? + y01.equals(0n).and(y[2].equals(0n)).assertFalse(); + let [f0, f1, f2] = split(f); + let f01 = collapse2([f0, f1]); + y01.equals(f01).and(y[2].equals(f2)).assertFalse(); + } + + return z; +} + function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md let f_ = (1n << L3) - f; @@ -274,6 +315,12 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { return { r01, r2, q, q2Bound }; } +function weakBound(x: Field, f: bigint) { + let f2 = f >> L2; + let f2Bound = (1n << L) - f2 - 1n; + return x.add(f2Bound); +} + function Field3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 8e57bde4bc..bdb485afd4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,6 +1,6 @@ import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { bitSlice, exists } from './common.js'; +import { bitSlice, exists, toVars } from './common.js'; export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; export { L, L2, L3, lMask, l2Mask }; @@ -67,6 +67,8 @@ function multiRangeCheck(x: Field, y: Field, z: Field) { } return; } + // ensure we are using pure variables + [x, y, z] = toVars([x, y, z]); let [x64, x76] = rangeCheck0Helper(x); let [y64, y76] = rangeCheck0Helper(y); @@ -91,6 +93,8 @@ function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { let [x, y] = splitCompactLimb(xy.toBigInt()); return [new Field(x), new Field(y), z]; } + // ensure we are using pure variables + [xy, z] = toVars([xy, z]); let [x, y] = exists(2, () => splitCompactLimb(xy.toBigInt())); From 4d8c4e10e5545dd94358ad630bbd43e72441450e Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 16:41:48 +0100 Subject: [PATCH 0593/1215] improve zkprogram analyzemethods return --- src/lib/proof_system.ts | 19 ++++++++++++++----- src/lib/proof_system.unit-test.ts | 16 +++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 377daf1a82..390f07337c 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -268,7 +268,9 @@ function ZkProgram< > ) => Promise; digest: () => string; - analyzeMethods: () => ReturnType[]; + analyzeMethods: () => { + [I in keyof Types]: ReturnType; + }; publicInputType: ProvableOrUndefined>; publicOutputType: ProvableOrVoid>; } & { @@ -302,9 +304,14 @@ function ZkProgram< let maxProofsVerified = getMaxProofsVerified(methodIntfs); function analyzeMethods() { - return methodIntfs.map((methodEntry, i) => - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) - ); + return Object.fromEntries( + methodIntfs.map((methodEntry, i) => [ + methodEntry.methodName, + analyzeMethod(publicInputType, methodEntry, methodFunctions[i]), + ]) + ) as any as { + [I in keyof Types]: ReturnType; + }; } let compileOutput: @@ -318,7 +325,9 @@ function ZkProgram< | undefined; async function compile({ cache = Cache.FileSystemDefault } = {}) { - let methodsMeta = analyzeMethods(); + let methodsMeta = methodIntfs.map((methodEntry, i) => + analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) + ); let gates = methodsMeta.map((m) => m.gates); let { provers, verify, verificationKey } = await compileProgram({ publicInputType, diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index 477d354114..b89f1f3dd3 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -16,14 +16,12 @@ const EmptyProgram = ZkProgram({ }); const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); -emptyMethodsMetadata.forEach((methodMetadata) => { - expect(methodMetadata).toEqual({ - rows: 0, - digest: '4f5ddea76d29cfcfd8c595f14e31f21b', - result: undefined, - gates: [], - publicInputSize: 0, - }); +expect(emptyMethodsMetadata.run).toEqual({ + rows: 0, + digest: '4f5ddea76d29cfcfd8c595f14e31f21b', + result: undefined, + gates: [], + publicInputSize: 0, }); class CounterPublicInput extends Struct({ @@ -47,5 +45,5 @@ const CounterProgram = ZkProgram({ }, }); -const incrementMethodMetadata = CounterProgram.analyzeMethods()[0]; +const incrementMethodMetadata = CounterProgram.analyzeMethods().increment; expect(incrementMethodMetadata).toEqual(expect.objectContaining({ rows: 18 })); From ca0f39aa4fe027bd78b763b663512eb6b42d7514 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 16:58:41 +0100 Subject: [PATCH 0594/1215] fix division --- src/lib/gadgets/foreign-field.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index bc9212c0c2..8eb5ff4375 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -202,16 +202,17 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { let z2Bound = weakBound(z[2], f); multiRangeCheck(q2Bound, z2Bound, new Field(0n)); - // check that r === y + // check that r === x // this means we don't have to range check r - let y01 = y[0].add(y[1].mul(1n << L)); - r01.assertEquals(y01); - r2.assertEquals(y[2]); + let x01 = x[0].add(x[1].mul(1n << L)); + r01.assertEquals(x01); + r2.assertEquals(x[2]); if (!allowZeroOverZero) { // assert that y != 0 mod f by checking that it doesn't equal 0 or f // this works because we assume y[2] <= f2 // TODO is this the most efficient way? + let y01 = y[0].add(y[1].mul(1n << L)); y01.equals(0n).and(y[2].equals(0n)).assertFalse(); let [f0, f1, f2] = split(f); let f01 = collapse2([f0, f1]); From 16d8dc4b71ef85c511cbbec169fc26800b3458e8 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 16:59:59 +0100 Subject: [PATCH 0595/1215] test inv and div --- src/lib/gadgets/foreign-field.unit-test.ts | 45 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index d62235d411..627c7deecb 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -50,11 +50,15 @@ for (let F of fields) { eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); - eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus)[0], 'mul'); + eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus), 'mul'); + equivalentProvable({ from: [f], to: f })( + (x) => F.inverse(x) ?? throwError('no inverse'), + (x) => ForeignField.inv(x, F.modulus) + ); eq2( - (x, y) => (x * y) / F.modulus, - (x, y) => ForeignField.mul(x, y, F.modulus)[1], - 'mul quotient' + (x, y) => F.div(x, y) ?? throwError('no inverse'), + (x, y) => ForeignField.div(x, y, F.modulus), + 'div' ); // sumchain of 5 @@ -103,13 +107,28 @@ let ffProgram = ZkProgram({ mul: { privateInputs: [Field3_, Field3_], method(x, y) { - let [r, _q] = ForeignField.mul(x, y, F.modulus); - return r; + return ForeignField.mul(x, y, F.modulus); + }, + }, + + inv: { + privateInputs: [Field3_], + method(x) { + return ForeignField.inv(x, F.modulus); + }, + }, + + div: { + privateInputs: [Field3_, Field3_], + method(x, y) { + return ForeignField.div(x, y, F.modulus); }, }, }, }); +// console.log(ffProgram.analyzeMethods()); + await ffProgram.compile(); await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 3 })( @@ -132,6 +151,16 @@ await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( 'prove mul' ); +await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( + (x, y) => F.div(x, y) ?? throwError('no inverse'), + async (x, y) => { + let proof = await ffProgram.div(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + return proof.publicOutput; + }, + 'prove div' +); + // helper function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { @@ -141,3 +170,7 @@ function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { } return sum; } + +function throwError(message: string): T { + throw Error(message); +} From dfed7016acb91ae1bdf08fb81b88b1dea53c516c Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 10 Nov 2023 17:13:05 +0100 Subject: [PATCH 0596/1215] start ec add --- src/lib/gadgets/elliptic-curve.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/lib/gadgets/elliptic-curve.ts diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts new file mode 100644 index 0000000000..9f2a3f1246 --- /dev/null +++ b/src/lib/gadgets/elliptic-curve.ts @@ -0,0 +1,49 @@ +import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; +import { provablePure } from '../circuit_value.js'; +import { Field } from '../field.js'; +import { Provable } from '../provable.js'; +import { TupleN } from '../util/types.js'; +import { Field3, ForeignField, bigint3 } from './foreign-field.js'; +import { multiRangeCheck } from './range-check.js'; + +type Point = { x: Field3; y: Field3 }; +type point = { x: bigint3; y: bigint3; infinity: boolean }; + +function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { + // m = (y2 - y1)/(x2 - x1) + let m = ForeignField.div( + // TODO bounds checks + ForeignField.sub(y2, y1, f), + ForeignField.sub(x2, x1, f), + f + ); + // x3 = m^2 - x1 - x2 + let m2 = ForeignField.mul(m, m, f); + let x3 = ForeignField.sumChain([m2, x1, x2], [-1n, -1n], f); + // y3 = m*(x1 - x3) - y1 + let y3 = ForeignField.sub( + ForeignField.mul(m, ForeignField.sub(x1, x3, f), f), + y1, + f + ); +} + +const Field3_ = provablePure([Field, Field, Field] as TupleN); + +let cs = Provable.constraintSystem(() => { + let x1 = Provable.witness(Field3_, () => ForeignField.from(0n)); + let x2 = Provable.witness(Field3_, () => ForeignField.from(0n)); + let y1 = Provable.witness(Field3_, () => ForeignField.from(0n)); + let y2 = Provable.witness(Field3_, () => ForeignField.from(0n)); + multiRangeCheck(...x1); + multiRangeCheck(...x2); + multiRangeCheck(...y1); + multiRangeCheck(...y2); + + let g = { x: x1, y: y1 }; + let h = { x: x2, y: y2 }; + + add(g, h, exampleFields.secp256k1.modulus); +}); + +console.log(cs); From d50cd017c2170253198394406f32bd2f5ec1b561 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 12 Nov 2023 22:02:34 +0100 Subject: [PATCH 0597/1215] much more efficient ecadd --- src/lib/gadgets/elliptic-curve.ts | 74 ++++++++++++++++++++++--------- src/lib/gadgets/foreign-field.ts | 54 +++++++++++++++++++--- 2 files changed, 102 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 9f2a3f1246..9048f99ae7 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,31 +1,67 @@ +import { inverse, mod } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { provablePure } from '../circuit_value.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { TupleN } from '../util/types.js'; -import { Field3, ForeignField, bigint3 } from './foreign-field.js'; +import { exists } from './common.js'; +import { + Field3, + ForeignField, + assertMul, + bigint3, + split, + weakBound, +} from './foreign-field.js'; import { multiRangeCheck } from './range-check.js'; type Point = { x: Field3; y: Field3 }; type point = { x: bigint3; y: bigint3; infinity: boolean }; +let { sumChain } = ForeignField; + function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { - // m = (y2 - y1)/(x2 - x1) - let m = ForeignField.div( - // TODO bounds checks - ForeignField.sub(y2, y1, f), - ForeignField.sub(x2, x1, f), - f - ); - // x3 = m^2 - x1 - x2 - let m2 = ForeignField.mul(m, m, f); - let x3 = ForeignField.sumChain([m2, x1, x2], [-1n, -1n], f); - // y3 = m*(x1 - x3) - y1 - let y3 = ForeignField.sub( - ForeignField.mul(m, ForeignField.sub(x1, x3, f), f), - y1, - f - ); + // witness and range-check slope, x3, y3 + let witnesses = exists(9, () => { + let [x1_, x2_, y1_, y2_] = ForeignField.toBigints(x1, x2, y1, y2); + let denom = inverse(mod(x1_ - x2_, f), f); + + let m = denom !== undefined ? mod((y1_ - y2_) * denom, f) : 0n; + let m2 = mod(m * m, f); + let x3 = mod(m2 - x1_ - x2_, f); + let y3 = mod(m * (x1_ - x3) - y1_, f); + + return [...split(m), ...split(x3), ...split(y3)]; + }); + let [m0, m1, m2, x30, x31, x32, y30, y31, y32] = witnesses; + let m: Field3 = [m0, m1, m2]; + let x3: Field3 = [x30, x31, x32]; + let y3: Field3 = [y30, y31, y32]; + + multiRangeCheck(...m); + multiRangeCheck(...x3); + multiRangeCheck(...y3); + let m2Bound = weakBound(m[2], f); + let x3Bound = weakBound(x3[2], f); + // we dont need to bounds check y3[2] because it's never one of the inputs to a multiplication + + // m*(x1 - x2) = y1 - y2 + let deltaX = sumChain([x1, x2], [-1n], f); + let deltaY = sumChain([y1, y2], [-1n], f, { skipRangeCheck: true }); + let qBound1 = assertMul(m, deltaX, deltaY, f); + + // m^2 = x1 + x2 + x3 + let xSum = sumChain([x1, x2, x3], [1n, 1n], f, { skipRangeCheck: true }); + let qBound2 = assertMul(m, m, xSum, f); + + // m*(x1 - x3) = y1 + y3 + let deltaX1X3 = sumChain([x1, x3], [-1n], f); + let ySum = sumChain([y1, y3], [1n], f, { skipRangeCheck: true }); + let qBound3 = assertMul(m, deltaX1X3, ySum, f); + + // bounds checks + multiRangeCheck(m2Bound, x3Bound, qBound1); + multiRangeCheck(qBound2, qBound3, Field.from(0n)); } const Field3_ = provablePure([Field, Field, Field] as TupleN); @@ -35,10 +71,6 @@ let cs = Provable.constraintSystem(() => { let x2 = Provable.witness(Field3_, () => ForeignField.from(0n)); let y1 = Provable.witness(Field3_, () => ForeignField.from(0n)); let y2 = Provable.witness(Field3_, () => ForeignField.from(0n)); - multiRangeCheck(...x1); - multiRangeCheck(...x2); - multiRangeCheck(...y1); - multiRangeCheck(...y2); let g = { x: x1, y: y1 }; let h = { x: x2, y: y2 }; diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 8eb5ff4375..2cb718ee3b 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -4,9 +4,8 @@ import { } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; -import { assert, bitSlice, exists, existsOne, toVar } from './common.js'; +import { assert, bitSlice, exists, toVar } from './common.js'; import { L, lMask, @@ -17,7 +16,16 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { ForeignField, Field3, bigint3, Sign }; +export { + ForeignField, + Field3, + bigint3, + Sign, + split, + collapse, + weakBound, + assertMul, +}; type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; @@ -50,6 +58,9 @@ const ForeignField = { toBigint(x: Field3): bigint { return collapse(bigint3(x)); }, + toBigints>(...xs: T) { + return Tuple.map(xs, ForeignField.toBigint); + }, }; /** @@ -57,7 +68,12 @@ const ForeignField = { * * assumes that inputs are range checked, does range check on the result. */ -function sumChain(x: Field3[], sign: Sign[], f: bigint) { +function sumChain( + x: Field3[], + sign: Sign[], + f: bigint, + { skipRangeCheck = false } = {} +) { assert(x.length === sign.length + 1, 'inputs and operators match'); // constant case @@ -76,7 +92,9 @@ function sumChain(x: Field3[], sign: Sign[], f: bigint) { Gates.zero(...result); // range check result - multiRangeCheck(...result); + if (!skipRangeCheck) { + multiRangeCheck(...result); + } return result; } @@ -143,6 +161,32 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { return r; } +function assertMul(x: Field3, y: Field3, xy: Field3, f: bigint) { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if ( + x.every((x) => x.isConstant()) && + y.every((x) => x.isConstant()) && + xy.every((x) => x.isConstant()) + ) { + let xy_ = mod(ForeignField.toBigint(x) * ForeignField.toBigint(y), f); + assert(xy_ === ForeignField.toBigint(xy), 'Expected xy to be x*y mod f'); + return Field.from(0n); + } + + // provable case + let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, y, f); + let xy01 = xy[0].add(xy[1].mul(1n << L)); + r01.assertEquals(xy01); + r2.assertEquals(xy[2]); + + // range check on quotient + multiRangeCheck(...q); + + return q2Bound; +} + function inverse(x: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); From b7fccabdec07e025864d63933f8c479349f0f3fc Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 12 Nov 2023 22:11:07 +0100 Subject: [PATCH 0598/1215] chain add into mul --- src/lib/gadgets/elliptic-curve.ts | 20 ++++++++++++++------ src/lib/gadgets/foreign-field.ts | 8 +++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 9048f99ae7..2fa86d30d8 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -45,19 +45,27 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { let x3Bound = weakBound(x3[2], f); // we dont need to bounds check y3[2] because it's never one of the inputs to a multiplication - // m*(x1 - x2) = y1 - y2 - let deltaX = sumChain([x1, x2], [-1n], f); + // (x1 - x2)*m = y1 - y2 let deltaY = sumChain([y1, y2], [-1n], f, { skipRangeCheck: true }); - let qBound1 = assertMul(m, deltaX, deltaY, f); + let deltaX = sumChain([x1, x2], [-1n], f, { + skipRangeCheck: true, + skipZeroRow: true, + }); + let qBound1 = assertMul(deltaX, m, deltaY, f); + multiRangeCheck(...deltaX); // m^2 = x1 + x2 + x3 let xSum = sumChain([x1, x2, x3], [1n, 1n], f, { skipRangeCheck: true }); let qBound2 = assertMul(m, m, xSum, f); - // m*(x1 - x3) = y1 + y3 - let deltaX1X3 = sumChain([x1, x3], [-1n], f); + // (x1 - x3)*m = y1 + y3 let ySum = sumChain([y1, y3], [1n], f, { skipRangeCheck: true }); - let qBound3 = assertMul(m, deltaX1X3, ySum, f); + let deltaX1X3 = sumChain([x1, x3], [-1n], f, { + skipRangeCheck: true, + skipZeroRow: true, + }); + let qBound3 = assertMul(deltaX1X3, m, ySum, f); + multiRangeCheck(...deltaX1X3); // bounds checks multiRangeCheck(m2Bound, x3Bound, qBound1); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 2cb718ee3b..652659b894 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -72,7 +72,7 @@ function sumChain( x: Field3[], sign: Sign[], f: bigint, - { skipRangeCheck = false } = {} + { skipRangeCheck = false, skipZeroRow = false } = {} ) { assert(x.length === sign.length + 1, 'inputs and operators match'); @@ -89,12 +89,10 @@ function sumChain( ({ result } = singleAdd(result, x[i + 1], sign[i], f)); } // final zero row to hold result - Gates.zero(...result); + if (!skipZeroRow) Gates.zero(...result); // range check result - if (!skipRangeCheck) { - multiRangeCheck(...result); - } + if (!skipRangeCheck) multiRangeCheck(...result); return result; } From 721456831888dff813b16be642fa8d18dcfc22ab Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 12 Nov 2023 19:37:24 -0800 Subject: [PATCH 0599/1215] fix(fetch.tx): increase returned blocks from bestChain gql request --- src/lib/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 8723d5ba1e..7907858f76 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -482,7 +482,7 @@ type LastBlockQueryFailureCheckResponse = { }; const lastBlockQueryFailureCheck = `{ - bestChain(maxLength: 1) { + bestChain(maxLength: 20) { transactions { zkappCommands { hash From 57b688e10b01b53bdb9721700383f0d7369fd7ce Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 12 Nov 2023 20:29:58 -0800 Subject: [PATCH 0600/1215] feat(fetch.ts): add length parameter to fetchLatestBlockZkappStatus query --- src/lib/fetch.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 7907858f76..48e081662d 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -481,8 +481,8 @@ type LastBlockQueryFailureCheckResponse = { }[]; }; -const lastBlockQueryFailureCheck = `{ - bestChain(maxLength: 20) { +const lastBlockQueryFailureCheck = (length: number) => `{ + bestChain(maxLength: ${length}) { transactions { zkappCommands { hash @@ -496,10 +496,11 @@ const lastBlockQueryFailureCheck = `{ }`; async function fetchLatestBlockZkappStatus( + blockLength: number, graphqlEndpoint = networkConfig.minaEndpoint ) { let [resp, error] = await makeGraphqlRequest( - lastBlockQueryFailureCheck, + lastBlockQueryFailureCheck(blockLength), graphqlEndpoint, networkConfig.minaFallbackEndpoints ); @@ -513,9 +514,8 @@ async function fetchLatestBlockZkappStatus( return bestChain; } -async function checkZkappTransaction(txnId: string) { - let bestChainBlocks = await fetchLatestBlockZkappStatus(); - +async function checkZkappTransaction(txnId: string, blockLength = 20) { + let bestChainBlocks = await fetchLatestBlockZkappStatus(blockLength); for (let block of bestChainBlocks.bestChain) { for (let zkappCommand of block.transactions.zkappCommands) { if (zkappCommand.hash === txnId) { @@ -542,7 +542,7 @@ async function checkZkappTransaction(txnId: string) { } return { success: false, - failureReason: null, + failureReason: `Transaction ${txnId} not found in the latest ${blockLength} blocks.`, }; } From ec4ff81733454bdcbf7a6c2395b31b9a8cd3f1b8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 12 Nov 2023 20:46:14 -0800 Subject: [PATCH 0601/1215] feat(CHANGELOG): add entry for checkZkappTransaction fix --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ff71d070..f6d00daa08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) -> No unreleased changes yet +### Fixed + +- Add a parameter to `checkZkappTransaction` for block length to check for transaction inclusion. This fixes a case where `Transaction.wait()` only checked the latest block, which led to an error once the transaction was included in a block that was not the latest. https://github.com/o1-labs/o1js/pull/1239 ## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) From e830e098da57775fdef67b4e1b0e6b1cb246a1ff Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 12 Nov 2023 22:07:09 -0800 Subject: [PATCH 0602/1215] fix(fetch.ts): revert failureReason message on transaction not found --- src/lib/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 48e081662d..768410c1b5 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -542,7 +542,7 @@ async function checkZkappTransaction(txnId: string, blockLength = 20) { } return { success: false, - failureReason: `Transaction ${txnId} not found in the latest ${blockLength} blocks.`, + failureReason: null, }; } From b53c875921beed4785158c778127659cd49dfc39 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 08:18:10 +0100 Subject: [PATCH 0603/1215] bindings with not operation --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f9f54a0a22..e6bd4ddfc2 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f9f54a0a229fae3c25ba4d44de08d465a18d7f03 +Subproject commit e6bd4ddfc2c1319428ac7c2fb4fd3e7f4862a81d From 7df0a789998599cd7087bc3050d78a77959b3dd4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 08:25:32 +0100 Subject: [PATCH 0604/1215] fixup type --- src/lib/gadgets/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index e2dfd5cd18..1d596d248b 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -54,7 +54,7 @@ function sumChain(x: Field3[], sign: Sign[], f: bigint) { Gates.zero(...result); // range check result - multiRangeCheck(...result); + multiRangeCheck(result); return result; } From 9a83e3cde72505202dcd54a83c52ca7f9ff8991b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 08:57:37 +0100 Subject: [PATCH 0605/1215] cleanup test --- src/lib/gadgets/foreign-field.unit-test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 0c173c8352..cb198dc87e 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -13,12 +13,11 @@ import { ForeignField, Field3, Sign } from './foreign-field.js'; import { ZkProgram } from '../proof_system.js'; import { Provable } from '../provable.js'; import { Field } from '../field.js'; -import { ProvableExtended, provable, provablePure } from '../circuit_value.js'; +import { provablePure } from '../circuit_value.js'; import { TupleN } from '../util/types.js'; import { assert } from './common.js'; const Field3_ = provablePure([Field, Field, Field] as TupleN); -const Sign = provable(BigInt) as ProvableExtended; function foreignField(F: FiniteField): Spec { let rng = Random.otherField(F); From d98e3a284231172eacd05be317888415043b511b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 11:24:15 +0100 Subject: [PATCH 0606/1215] first version of constraint system test dsl --- src/lib/testing/constraint-system.ts | 206 +++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/lib/testing/constraint-system.ts diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts new file mode 100644 index 0000000000..85773bf1f0 --- /dev/null +++ b/src/lib/testing/constraint-system.ts @@ -0,0 +1,206 @@ +/** + * DSL for testing that a gadget generates the expected constraint system. + * + * An essential feature is that `constraintSystem()` automatically generates a + * variety of fieldvar types for the inputs: constants, variables, and combinators. + */ +import { Gate, GateType } from '../../snarky.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; +import { Field, FieldType, FieldVar } from '../field.js'; +import { Provable } from '../provable.js'; +import { Tuple } from '../util/types.js'; +import { Random } from './random.js'; +import { test } from './property.js'; + +export { constraintSystem, contains, log }; + +type CsVarSpec = Provable | { provable: Provable }; +type InferCsVar = T extends { provable: Provable } + ? U + : T extends Provable + ? U + : never; +type CsParams>> = { + [k in keyof In]: InferCsVar; +}; + +type CsTest = { run: (cs: Gate[]) => boolean; label: string }; + +// main DSL + +function constraintSystem>>( + inputs: { from: In }, + main: (...args: CsParams) => void, + tests: CsTest[] +) { + // create random generators + let types = inputs.from.map(provable); + let rngs = types.map(layout); + + test(...rngs, (...args) => { + let layouts = args.slice(0, -1); + + // compute the constraint system + let result = Provable.constraintSystem(() => { + // each random input "layout" has to be instantiated into vars in this circuit + let values = types.map((type, i) => + instantiate(type, layouts[i]) + ) as CsParams; + main(...values); + }); + + // run tests + let failures = tests + .map((test) => [test.run(result.gates), test.label] as const) + .filter(([ok]) => !ok) + .map(([, label]) => label); + + if (failures.length > 0) { + console.log('Constraint system:'); + console.log(result.gates); + + let s = failures.length === 1 ? '' : 's'; + throw Error( + `Failed constraint system test${s}:\n${failures.join('\n')}\n` + ); + } + }); +} + +// DSL for writing tests + +/** + * Test that constraint system contains each of a list of gates consecutively. + * + * You can also pass a list of lists. In that case, the constraint system has to contain + * each of the lists of gates in the given order, but not necessarily consecutively. + * + * @example + * ```ts + * // constraint system contains a Rot64 gate + * contains('Rot64') + * + * // constraint system contains a Rot64 gate, followed directly by a RangeCheck0 gate + * contains(['Rot64', 'RangeCheck0']) + * + * // constraint system contains two instances of the combination [Rot64, RangeCheck0] + * contains([['Rot64', 'RangeCheck0'], ['Rot64', 'RangeCheck0']]]) + * ``` + */ +function contains(gates: GateType | GateType[] | GateType[][]): CsTest { + let expectedGatess = toGatess(gates); + return { + run(cs) { + let gates = cs.map((g) => g.type); + let i = 0; + let j = 0; + for (let gate of gates) { + if (gate === expectedGatess[i][j]) { + j++; + if (j === expectedGatess[i].length) { + i++; + j = 0; + if (i === expectedGatess.length) return true; + } + } else if (gate === expectedGatess[i][0]) { + j = 1; + } else { + j = 0; + } + } + return false; + }, + label: `contains ${JSON.stringify(expectedGatess)}`, + }; +} + +/** + * "Test" that just logs the constraint system. + */ +const log: CsTest = { + run(cs) { + console.log('Constraint system:'); + console.log(cs); + return true; + }, + label: '', +}; + +function toGatess( + gateTypes: GateType | GateType[] | GateType[][] +): GateType[][] { + if (typeof gateTypes === 'string') return [[gateTypes]]; + if (Array.isArray(gateTypes[0])) return gateTypes as GateType[][]; + return [gateTypes as GateType[]]; +} + +// Random generator for arbitrary provable types + +function provable(spec: CsVarSpec): Provable { + return 'provable' in spec ? spec.provable : spec; +} + +function layout(type: Provable): Random { + let length = type.sizeInFields(); + + return Random(() => { + let fields = Array.from({ length }, () => new Field(drawFieldVar())); + return type.fromFields(fields, type.toAuxiliary()); + }); +} + +function instantiate(type: Provable, value: T) { + let fields = type.toFields(value).map((x) => instantiateFieldVar(x.value)); + return type.fromFields(fields, type.toAuxiliary()); +} + +// Random generator for fieldvars that exercises constants, variables and combinators + +function drawFieldVar(): FieldVar { + let fieldType = drawFieldType(); + switch (fieldType) { + case FieldType.Constant: { + return [FieldType.Constant, [0, 17n]]; + } + case FieldType.Var: { + return [FieldType.Var, 0]; + } + case FieldType.Add: { + let x = drawFieldVar(); + let y = drawFieldVar(); + return [FieldType.Add, x, y]; + } + case FieldType.Scale: { + let x = drawFieldVar(); + return [FieldType.Scale, [0, 3n], x]; + } + } +} + +function instantiateFieldVar(x: FieldVar): Field { + switch (x[0]) { + case FieldType.Constant: { + return new Field(x); + } + case FieldType.Var: { + return Provable.witness(Field, () => Field.from(0n)); + } + case FieldType.Add: { + let a = instantiateFieldVar(x[1]); + let b = instantiateFieldVar(x[2]); + return a.add(b); + } + case FieldType.Scale: { + let a = instantiateFieldVar(x[2]); + return a.mul(x[1][1]); + } + } +} + +function drawFieldType(): FieldType { + let oneOf8 = randomBytes(1)[0] & 0b111; + if (oneOf8 < 4) return FieldType.Var; + if (oneOf8 < 6) return FieldType.Constant; + if (oneOf8 === 6) return FieldType.Scale; + return FieldType.Add; +} From 1e5919b616ab737012bdb0fc095281b1b8b67c25 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:05:58 +0100 Subject: [PATCH 0607/1215] properly implement fieldvar combinators --- src/lib/field.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 5d810ef20f..640bed46b7 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -78,13 +78,22 @@ const FieldVar = { isConstant(x: FieldVar): x is ConstantFieldVar { return x[0] === FieldType.Constant; }, - // TODO: handle (special) constants add(x: FieldVar, y: FieldVar): FieldVar { + if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; + if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; + if (FieldVar.isConstant(x) && FieldVar.isConstant(y)) { + return FieldVar.constant(Fp.add(x[1][1], y[1][1])); + } return [FieldType.Add, x, y]; }, - // TODO: handle (special) constants - scale(c: FieldConst, x: FieldVar): FieldVar { - return [FieldType.Scale, c, x]; + scale(c: bigint | FieldConst, x: FieldVar): FieldVar { + let c0 = typeof c === 'bigint' ? FieldConst.fromBigint(c) : c; + if (c0[1] === 0n) return FieldVar.constant(0n); + if (c0[1] === 1n) return x; + if (FieldVar.isConstant(x)) { + return FieldVar.constant(Fp.mul(c0[1], x[1][1])); + } + return [FieldType.Scale, c0, x]; }, [0]: [FieldType.Constant, FieldConst[0]] satisfies ConstantFieldVar, [1]: [FieldType.Constant, FieldConst[1]] satisfies ConstantFieldVar, From 4605ad276d6f8179fbd0c6693cb507760cf1fb9f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:08:44 +0100 Subject: [PATCH 0608/1215] pass inputs to cs tests, and make them combinable --- src/lib/testing/constraint-system.ts | 96 ++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 85773bf1f0..205d013e94 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -12,7 +12,7 @@ import { Tuple } from '../util/types.js'; import { Random } from './random.js'; import { test } from './property.js'; -export { constraintSystem, contains, log }; +export { constraintSystem, not, and, or, contains, allConstant, log }; type CsVarSpec = Provable | { provable: Provable }; type InferCsVar = T extends { provable: Provable } @@ -23,15 +23,14 @@ type InferCsVar = T extends { provable: Provable } type CsParams>> = { [k in keyof In]: InferCsVar; }; - -type CsTest = { run: (cs: Gate[]) => boolean; label: string }; +type TypeAndValue = { type: Provable; value: T }; // main DSL function constraintSystem>>( inputs: { from: In }, main: (...args: CsParams) => void, - tests: CsTest[] + csTest: CsTest ) { // create random generators let types = inputs.from.map(provable); @@ -41,7 +40,7 @@ function constraintSystem>>( let layouts = args.slice(0, -1); // compute the constraint system - let result = Provable.constraintSystem(() => { + let { gates } = Provable.constraintSystem(() => { // each random input "layout" has to be instantiated into vars in this circuit let values = types.map((type, i) => instantiate(type, layouts[i]) @@ -50,14 +49,13 @@ function constraintSystem>>( }); // run tests - let failures = tests - .map((test) => [test.run(result.gates), test.label] as const) - .filter(([ok]) => !ok) - .map(([, label]) => label); + let typesAndValues = types.map((type, i) => ({ type, value: layouts[i] })); + + let { ok, failures } = run(csTest, gates, typesAndValues); - if (failures.length > 0) { + if (!ok) { console.log('Constraint system:'); - console.log(result.gates); + console.log(gates); let s = failures.length === 1 ? '' : 's'; throw Error( @@ -69,6 +67,64 @@ function constraintSystem>>( // DSL for writing tests +type CsTestBase = { + run: (cs: Gate[], inputs: TypeAndValue[]) => boolean; + label: string; +}; +type Base = { kind?: undefined } & CsTestBase; +type Not = { kind: 'not' } & CsTestBase; +type And = { kind: 'and'; tests: CsTest[] }; +type Or = { kind: 'or'; tests: CsTest[] }; +type CsTest = Base | Not | And | Or; + +type Result = { ok: boolean; failures: string[] }; + +function run(test: CsTest, cs: Gate[], inputs: TypeAndValue[]): Result { + switch (test.kind) { + case undefined: { + let ok = test.run(cs, inputs); + let failures = ok ? [] : [test.label]; + return { ok, failures }; + } + case 'not': { + let ok = test.run(cs, inputs); + let failures = ok ? [`not(${test.label})`] : []; + return { ok: !ok, failures }; + } + case 'and': { + let results = test.tests.map((t) => run(t, cs, inputs)); + let ok = results.every((r) => r.ok); + let failures = results.flatMap((r) => r.failures); + return { ok, failures }; + } + case 'or': { + let results = test.tests.map((t) => run(t, cs, inputs)); + let ok = results.some((r) => r.ok); + let failures = results.flatMap((r) => r.failures); + return { ok, failures }; + } + } +} + +/** + * Negate a test. + */ +function not(test: CsTest): CsTest { + return { kind: 'not', ...test }; +} +/** + * Check that all input tests pass. + */ +function and(...tests: CsTest[]): CsTest { + return { kind: 'and', tests }; +} +/** + * Check that at least one input test passes. + */ +function or(...tests: CsTest[]): CsTest { + return { kind: 'or', tests }; +} + /** * Test that constraint system contains each of a list of gates consecutively. * @@ -114,6 +170,18 @@ function contains(gates: GateType | GateType[] | GateType[][]): CsTest { }; } +/** + * Test whether all inputs are constant. + */ +const allConstant: CsTest = { + run(cs, inputs) { + return inputs.every(({ type, value }) => + type.toFields(value).every((x) => x.isConstant()) + ); + }, + label: 'all inputs constant', +}; + /** * "Test" that just logs the constraint system. */ @@ -160,7 +228,7 @@ function drawFieldVar(): FieldVar { let fieldType = drawFieldType(); switch (fieldType) { case FieldType.Constant: { - return [FieldType.Constant, [0, 17n]]; + return FieldVar.constant(17n); } case FieldType.Var: { return [FieldType.Var, 0]; @@ -168,11 +236,11 @@ function drawFieldVar(): FieldVar { case FieldType.Add: { let x = drawFieldVar(); let y = drawFieldVar(); - return [FieldType.Add, x, y]; + return FieldVar.add(x, y); } case FieldType.Scale: { let x = drawFieldVar(); - return [FieldType.Scale, [0, 3n], x]; + return FieldVar.scale(3n, x); } } } From 4f682509e74e9dc88e5af2c2f87dc71027192a16 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:25:14 +0100 Subject: [PATCH 0609/1215] more cs tests and combinators --- src/lib/testing/constraint-system.ts | 68 ++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 205d013e94..6060ff00f0 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -12,7 +12,19 @@ import { Tuple } from '../util/types.js'; import { Random } from './random.js'; import { test } from './property.js'; -export { constraintSystem, not, and, or, contains, allConstant, log }; +export { + constraintSystem, + not, + and, + or, + equals, + contains, + allConstant, + ifNotAllConstant, + isEmpty, + withoutGenerics, + log, +}; type CsVarSpec = Provable | { provable: Provable }; type InferCsVar = T extends { provable: Provable } @@ -73,8 +85,8 @@ type CsTestBase = { }; type Base = { kind?: undefined } & CsTestBase; type Not = { kind: 'not' } & CsTestBase; -type And = { kind: 'and'; tests: CsTest[] }; -type Or = { kind: 'or'; tests: CsTest[] }; +type And = { kind: 'and'; tests: CsTest[]; label: string }; +type Or = { kind: 'or'; tests: CsTest[]; label: string }; type CsTest = Base | Not | And | Or; type Result = { ok: boolean; failures: string[] }; @@ -116,13 +128,25 @@ function not(test: CsTest): CsTest { * Check that all input tests pass. */ function and(...tests: CsTest[]): CsTest { - return { kind: 'and', tests }; + return { kind: 'and', tests, label: `and(${tests.map((t) => t.label)})` }; } /** * Check that at least one input test passes. */ function or(...tests: CsTest[]): CsTest { - return { kind: 'or', tests }; + return { kind: 'or', tests, label: `or(${tests.map((t) => t.label)})` }; +} + +/** + * Test for precise equality of the constraint system with a given list of gates. + */ +function equals(gates: GateType[]): CsTest { + return { + run(cs) { + return cs.every((g, i) => g.type === gates[i]); + }, + label: `equals ${JSON.stringify(gates)}`, + }; } /** @@ -182,6 +206,40 @@ const allConstant: CsTest = { label: 'all inputs constant', }; +/** + * Modifies a test so that it doesn't fail if all inputs are constant, and instead + * checks that the constraint system is empty in that case. + */ +function ifNotAllConstant(test: CsTest): CsTest { + return or(test, and(allConstant, isEmpty)); +} + +/** + * Test whether all inputs are constant. + */ +const isEmpty: CsTest = { + run(cs) { + return cs.length === 0; + }, + label: 'cs is empty', +}; + +/** + * Modifies a test so that it runs on the constraint system with generic gates filtered out. + */ +function withoutGenerics(test: CsTest): CsTest { + return { + run(cs, inputs) { + return run( + test, + cs.filter((g) => g.type !== 'Generic'), + inputs + ).ok; + }, + label: `withoutGenerics(${test.label})`, + }; +} + /** * "Test" that just logs the constraint system. */ From c6c4f97ed8185e933e4642cf3d0b4c45423791bf Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:27:33 +0100 Subject: [PATCH 0610/1215] replace existing test with new dsl test, which passes --- src/lib/gadgets/range-check.unit-test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index c66c6a806d..f526197c71 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -14,6 +14,12 @@ import { assert, exists } from './common.js'; import { Gadgets } from './gadgets.js'; import { L } from './range-check.js'; import { expect } from 'expect'; +import { + constraintSystem, + equals, + ifNotAllConstant, + withoutGenerics, +} from '../testing/constraint-system.js'; let uint = (n: number | bigint): Spec => { let uint = Random.bignat((1n << BigInt(n)) - 1n); @@ -29,14 +35,16 @@ let maybeUint = (n: number | bigint): Spec => { // constraint system sanity check +constraintSystem( + { from: [Field] }, + (x) => Gadgets.rangeCheck64(x), + ifNotAllConstant(withoutGenerics(equals(['RangeCheck0']))) +); + function csWithoutGenerics(gates: Gate[]) { return gates.map((g) => g.type).filter((type) => type !== 'Generic'); } -let check64 = Provable.constraintSystem(() => { - let [x] = exists(1, () => [0n]); - Gadgets.rangeCheck64(x); -}); let multi = Provable.constraintSystem(() => { let x = exists(3, () => [0n, 0n, 0n]); Gadgets.multiRangeCheck(x); @@ -46,10 +54,8 @@ let compact = Provable.constraintSystem(() => { Gadgets.compactMultiRangeCheck(xy, z); }); -let expectedLayout64 = ['RangeCheck0']; let expectedLayoutMulti = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; -expect(csWithoutGenerics(check64.gates)).toEqual(expectedLayout64); expect(csWithoutGenerics(multi.gates)).toEqual(expectedLayoutMulti); expect(csWithoutGenerics(compact.gates)).toEqual(expectedLayoutMulti); From d17d47f20b288edffcc3309f0e00f4ba361a7b18 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:35:24 +0100 Subject: [PATCH 0611/1215] usability tweaks --- src/lib/testing/constraint-system.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 6060ff00f0..bb46406d28 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -67,6 +67,7 @@ function constraintSystem>>( if (!ok) { console.log('Constraint system:'); + // TODO nice printed representation of cs console.log(gates); let s = failures.length === 1 ? '' : 's'; @@ -167,7 +168,9 @@ function equals(gates: GateType[]): CsTest { * contains([['Rot64', 'RangeCheck0'], ['Rot64', 'RangeCheck0']]]) * ``` */ -function contains(gates: GateType | GateType[] | GateType[][]): CsTest { +function contains( + gates: GateType | readonly GateType[] | readonly GateType[][] +): CsTest { let expectedGatess = toGatess(gates); return { run(cs) { @@ -253,7 +256,7 @@ const log: CsTest = { }; function toGatess( - gateTypes: GateType | GateType[] | GateType[][] + gateTypes: GateType | readonly GateType[] | readonly GateType[][] ): GateType[][] { if (typeof gateTypes === 'string') return [[gateTypes]]; if (Array.isArray(gateTypes[0])) return gateTypes as GateType[][]; From ff73b233a0288f5097e3e62e4858cc666aca2620 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 12:58:30 +0100 Subject: [PATCH 0612/1215] tweak error output --- src/lib/testing/constraint-system.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index bb46406d28..f145ac477f 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -40,6 +40,7 @@ type TypeAndValue = { type: Provable; value: T }; // main DSL function constraintSystem>>( + label: string, inputs: { from: In }, main: (...args: CsParams) => void, csTest: CsTest @@ -70,9 +71,10 @@ function constraintSystem>>( // TODO nice printed representation of cs console.log(gates); - let s = failures.length === 1 ? '' : 's'; throw Error( - `Failed constraint system test${s}:\n${failures.join('\n')}\n` + `Constraint system test: ${label}\n\n${failures + .map((f) => `FAIL: ${f}`) + .join('\n')}\n` ); } }); @@ -107,13 +109,13 @@ function run(test: CsTest, cs: Gate[], inputs: TypeAndValue[]): Result { case 'and': { let results = test.tests.map((t) => run(t, cs, inputs)); let ok = results.every((r) => r.ok); - let failures = results.flatMap((r) => r.failures); + let failures = ok ? [] : results.flatMap((r) => r.failures); return { ok, failures }; } case 'or': { let results = test.tests.map((t) => run(t, cs, inputs)); let ok = results.some((r) => r.ok); - let failures = results.flatMap((r) => r.failures); + let failures = ok ? [] : results.flatMap((r) => r.failures); return { ok, failures }; } } From 657d28fbe26716613d3e27f2d56c69204197f1a0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 13:02:00 +0100 Subject: [PATCH 0613/1215] replace other test and expose bug where generic gate disrupts mrc chain --- src/lib/gadgets/range-check.unit-test.ts | 41 ++++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index f526197c71..b5d5110807 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -1,8 +1,6 @@ -import type { Gate } from '../../snarky.js'; import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../../lib/core.js'; import { ZkProgram } from '../proof_system.js'; -import { Provable } from '../provable.js'; import { Spec, boolean, @@ -10,12 +8,12 @@ import { fieldWithRng, } from '../testing/equivalent.js'; import { Random } from '../testing/property.js'; -import { assert, exists } from './common.js'; +import { assert } from './common.js'; import { Gadgets } from './gadgets.js'; import { L } from './range-check.js'; -import { expect } from 'expect'; import { constraintSystem, + contains, equals, ifNotAllConstant, withoutGenerics, @@ -36,28 +34,29 @@ let maybeUint = (n: number | bigint): Spec => { // constraint system sanity check constraintSystem( + 'range check 64', { from: [Field] }, - (x) => Gadgets.rangeCheck64(x), + Gadgets.rangeCheck64, ifNotAllConstant(withoutGenerics(equals(['RangeCheck0']))) ); -function csWithoutGenerics(gates: Gate[]) { - return gates.map((g) => g.type).filter((type) => type !== 'Generic'); -} - -let multi = Provable.constraintSystem(() => { - let x = exists(3, () => [0n, 0n, 0n]); - Gadgets.multiRangeCheck(x); -}); -let compact = Provable.constraintSystem(() => { - let [xy, z] = exists(2, () => [0n, 0n]); - Gadgets.compactMultiRangeCheck(xy, z); -}); - -let expectedLayoutMulti = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; +constraintSystem( + 'multi-range check', + { from: [Field, Field, Field] }, + (x, y, z) => Gadgets.multiRangeCheck([x, y, z]), + ifNotAllConstant( + contains(['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']) + ) +); -expect(csWithoutGenerics(multi.gates)).toEqual(expectedLayoutMulti); -expect(csWithoutGenerics(compact.gates)).toEqual(expectedLayoutMulti); +constraintSystem( + 'compact multi-range check', + { from: [Field, Field] }, + Gadgets.compactMultiRangeCheck, + ifNotAllConstant( + contains(['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']) + ) +); // TODO: make a ZkFunction or something that doesn't go through Pickles // -------------------------- From 1e4a1a017b042e254e49888e100d30b5dc85723d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 14:59:13 +0100 Subject: [PATCH 0614/1215] print constraint system --- src/lib/testing/constraint-system.ts | 59 +++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index f145ac477f..6b7962534f 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -68,8 +68,7 @@ function constraintSystem>>( if (!ok) { console.log('Constraint system:'); - // TODO nice printed representation of cs - console.log(gates); + printGates(gates); throw Error( `Constraint system test: ${label}\n\n${failures @@ -335,3 +334,59 @@ function drawFieldType(): FieldType { if (oneOf8 === 6) return FieldType.Scale; return FieldType.Add; } + +// print a constraint system + +function printGates(gates: Gate[]) { + for (let i = 0, n = gates.length; i < n; i++) { + let { type, wires, coeffs } = gates[i]; + console.log( + i.toString().padEnd(4, ' '), + type.padEnd(15, ' '), + coeffsToPretty(type, coeffs).padEnd(30, ' '), + wiresToPretty(wires, i) + ); + } + console.log(); +} + +let minusRange = Field.ORDER - (1n << 64n); + +function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { + if (coeffs.length === 0) return ''; + if (type === 'Generic' && coeffs.length > 5) { + let first = coeffsToPretty(type, coeffs.slice(0, 5)); + let second = coeffsToPretty(type, coeffs.slice(5)); + return `${first} ${second}`; + } + if (type === 'Poseidon' && coeffs.length > 3) { + return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; + } + let str = coeffs + .map((c) => { + let c0 = BigInt(c); + if (c0 > minusRange) c0 -= Field.ORDER; + let cStr = c0.toString(); + if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; + return cStr; + }) + .join(' '); + return `[${str}]`; +} + +function wiresToPretty(wires: Gate['wires'], row: number) { + let strWires: string[] = []; + let n = wires.length; + for (let col = 0; col < n; col++) { + let wire = wires[col]; + if (wire.row === row && wire.col === col) continue; + if (wire.row === row) { + strWires.push(`${col}->${wire.col}`); + } else { + let rowDelta = wire.row - row; + let rowStr = rowDelta > 0 ? `+${rowDelta}` : `${rowDelta}`; + strWires.push(`${col}->(${rowStr},${wire.col})`); + } + } + return strWires.join(', '); +} From dfffeda1d6a14c25aca9ab951678df880c8b5770 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 15:31:02 +0100 Subject: [PATCH 0615/1215] add utils to flush generic gates early --- src/lib/gadgets/common.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 6b97c6016b..acffca9349 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,6 +1,6 @@ import { Provable } from '../provable.js'; -import { Field, FieldConst } from '../field.js'; -import { TupleN } from '../util/types.js'; +import { Field, FieldConst, FieldVar, FieldType } from '../field.js'; +import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; @@ -9,6 +9,9 @@ const MAX_BITS = 64 as const; export { MAX_BITS, exists, + existsOne, + toVars, + toVar, assert, bitSlice, witnessSlice, @@ -16,6 +19,11 @@ export { divideWithRemainder, }; +function existsOne(compute: () => bigint) { + let varMl = Snarky.existsVar(() => FieldConst.fromBigint(compute())); + return new Field(varMl); +} + function exists TupleN>( n: N, compute: C @@ -27,6 +35,31 @@ function exists TupleN>( return TupleN.fromArray(n, vars); } +/** + * Given a Field, collapse its AST to a pure Var. See {@link FieldVar}. + * + * This is useful to prevent rogue Generic gates added in the middle of gate chains, + * which are caused by snarky auto-resolving constants, adds and scales to vars. + * + * Same as `Field.seal()` with the difference that `seal()` leaves constants as is. + */ +function toVar(x: Field | bigint) { + // don't change existing vars + if (x instanceof Field && x.value[1] === FieldType.Var) return x; + let xVar = existsOne(() => Field.from(x).toBigInt()); + xVar.assertEquals(x); + return xVar; +} + +/** + * Apply {@link toVar} to each element of a tuple. + */ +function toVars>( + fields: T +): { [k in keyof T]: Field } { + return Tuple.map(fields, toVar); +} + function assert(stmt: boolean, message?: string) { if (!stmt) { throw Error(message ?? 'Assertion failed'); From 2604f54e375fe551565ec38ce9a0a260c3d0f2bc Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 15:32:26 +0100 Subject: [PATCH 0616/1215] fix range check gadgets --- src/lib/gadgets/range-check.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 1f4f369157..004a6cf438 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,6 +1,6 @@ import { Field } from '../field.js'; import * as Gates from '../gates.js'; -import { bitSlice, exists } from './common.js'; +import { bitSlice, exists, toVar, toVars } from './common.js'; export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck, L }; @@ -64,10 +64,13 @@ function multiRangeCheck([x, y, z]: [Field, Field, Field]) { } return; } + // ensure we are using pure variables + [x, y, z] = toVars([x, y, z]); + let zero = toVar(0n); let [x64, x76] = rangeCheck0Helper(x); let [y64, y76] = rangeCheck0Helper(y); - rangeCheck1Helper({ x64, x76, y64, y76, z, yz: new Field(0) }); + rangeCheck1Helper({ x64, x76, y64, y76, z, yz: zero }); } /** @@ -88,6 +91,8 @@ function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { let [x, y] = splitCompactLimb(xy.toBigInt()); return [new Field(x), new Field(y), z]; } + // ensure we are using pure variables + [xy, z] = toVars([xy, z]); let [x, y] = exists(2, () => splitCompactLimb(xy.toBigInt())); From fb5ad69b6cdf747c9775b116177f898880929ec6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:00:46 +0100 Subject: [PATCH 0617/1215] fix equals, usability tweaks --- src/lib/testing/constraint-system.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 6b7962534f..858fa698f5 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -23,7 +23,8 @@ export { ifNotAllConstant, isEmpty, withoutGenerics, - log, + print, + repeat, }; type CsVarSpec = Provable | { provable: Provable }; @@ -142,9 +143,10 @@ function or(...tests: CsTest[]): CsTest { /** * Test for precise equality of the constraint system with a given list of gates. */ -function equals(gates: GateType[]): CsTest { +function equals(gates: readonly GateType[]): CsTest { return { run(cs) { + if (cs.length !== gates.length) return false; return cs.every((g, i) => g.type === gates[i]); }, label: `equals ${JSON.stringify(gates)}`, @@ -247,15 +249,20 @@ function withoutGenerics(test: CsTest): CsTest { /** * "Test" that just logs the constraint system. */ -const log: CsTest = { +const print: CsTest = { run(cs) { console.log('Constraint system:'); - console.log(cs); + printGates(cs); return true; }, label: '', }; +function repeat(n: number, gates: GateType | GateType[]): GateType[] { + gates = Array.isArray(gates) ? gates : [gates]; + return Array(n).fill(gates).flat(); +} + function toGatess( gateTypes: GateType | readonly GateType[] | readonly GateType[][] ): GateType[][] { From ad137ed9817c873b8071394a9c1b6406734752f4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:00:57 +0100 Subject: [PATCH 0618/1215] expose raw methods from zkprogram --- src/lib/proof_system.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 3cd395d76d..829d824f00 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -271,6 +271,13 @@ function ZkProgram< analyzeMethods: () => ReturnType[]; publicInputType: ProvableOrUndefined>; publicOutputType: ProvableOrVoid>; + rawMethods: { + [I in keyof Types]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Types[I] + >['method']; + }; } & { [I in keyof Types]: Prover< InferProvableOrUndefined>, @@ -427,6 +434,9 @@ function ZkProgram< Get >, analyzeMethods, + rawMethods: Object.fromEntries( + Object.entries(methods).map(([k, v]) => [k, v.method]) + ) as any, }, provers ); From f4a0bfe10859959db98de2dcdf01957d933c51dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:04:42 +0100 Subject: [PATCH 0619/1215] test bitwise cs, fix rot, clean up not --- src/lib/gadgets/bitwise.ts | 15 +++--- src/lib/gadgets/bitwise.unit-test.ts | 70 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index c2082b7172..d4fc7ff5f5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -8,6 +8,7 @@ import { witnessSlice, witnessNextValue, divideWithRemainder, + toVar, } from './common.js'; import { rangeCheck64 } from './range-check.js'; @@ -37,18 +38,12 @@ function not(a: Field, length: number, checked: boolean = false) { } // create a bitmask with all ones - let allOnesF = new Field(2n ** BigInt(length) - 1n); - - let allOnes = Provable.witness(Field, () => { - return allOnesF; - }); - - allOnesF.assertEquals(allOnes); + let allOnes = new Field(2n ** BigInt(length) - 1n); if (checked) { return xor(a, allOnes, length); } else { - return allOnes.sub(a); + return allOnes.sub(a).seal(); } } @@ -252,6 +247,10 @@ function rot( } ); + // flush zero var to prevent broken gate chain (zero is used in rangeCheck64) + // TODO this is an abstraction leak, but not clear to me how to improve + toVar(0n); + // Compute current row Gates.rotate( field, diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 277fc17efc..6fa914a799 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -9,6 +9,16 @@ import { Fp, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; import { Random } from '../testing/property.js'; +import { + constraintSystem, + contains, + equals, + ifNotAllConstant, + repeat, + and, + withoutGenerics, +} from '../testing/constraint-system.js'; +import { GateType } from '../../snarky.js'; const maybeField = { ...field, @@ -183,3 +193,63 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return proof.publicOutput; } ); + +// check that gate chains stay intact + +function xorChain(bits: number) { + return repeat(Math.ceil(bits / 16), 'Xor16').concat('Zero'); +} + +constraintSystem( + 'xor', + { from: [Field, Field] }, + Bitwise.rawMethods.xor, + ifNotAllConstant(contains(xorChain(254))) +); + +constraintSystem( + 'not checked', + { from: [Field] }, + Bitwise.rawMethods.notChecked, + ifNotAllConstant(contains(xorChain(254))) +); + +constraintSystem( + 'not unchecked', + { from: [Field] }, + Bitwise.rawMethods.notUnchecked, + ifNotAllConstant(contains('Generic')) +); + +constraintSystem( + 'and', + { from: [Field, Field] }, + Bitwise.rawMethods.and, + ifNotAllConstant(contains(xorChain(64))) +); + +let rotChain: GateType[] = ['Rot64', 'RangeCheck0', 'RangeCheck0']; +let isJustRotate = ifNotAllConstant( + and(contains(rotChain), withoutGenerics(equals(rotChain))) +); + +constraintSystem( + 'rotate', + { from: [Field] }, + Bitwise.rawMethods.rot, + isJustRotate +); + +constraintSystem( + 'left shift', + { from: [Field] }, + Bitwise.rawMethods.leftShift, + isJustRotate +); + +constraintSystem( + 'right shift', + { from: [Field] }, + Bitwise.rawMethods.rightShift, + isJustRotate +); From 9759938087bde2d5fd7e57454a2bdabb54e9ceef Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:05:03 +0100 Subject: [PATCH 0620/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index c5aa5f163b..5df84bf1f0 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c5aa5f163bf9e86adea3b85cdb75d84e56b9d461 +Subproject commit 5df84bf1f06c9c1e984e19d067fcf10c8ae53299 From 82e5d28a6b1c94a667de76a29afbc0a147000ee3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:07:24 +0100 Subject: [PATCH 0621/1215] more verbose but clearer type name --- src/lib/testing/constraint-system.ts | 41 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 858fa698f5..aafe87e38d 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -25,6 +25,7 @@ export { withoutGenerics, print, repeat, + ConstraintSystemTest, }; type CsVarSpec = Provable | { provable: Provable }; @@ -44,7 +45,7 @@ function constraintSystem>>( label: string, inputs: { from: In }, main: (...args: CsParams) => void, - csTest: CsTest + csTest: ConstraintSystemTest ) { // create random generators let types = inputs.from.map(provable); @@ -82,19 +83,23 @@ function constraintSystem>>( // DSL for writing tests -type CsTestBase = { +type ConstraintSystemTestBase = { run: (cs: Gate[], inputs: TypeAndValue[]) => boolean; label: string; }; -type Base = { kind?: undefined } & CsTestBase; -type Not = { kind: 'not' } & CsTestBase; -type And = { kind: 'and'; tests: CsTest[]; label: string }; -type Or = { kind: 'or'; tests: CsTest[]; label: string }; -type CsTest = Base | Not | And | Or; +type Base = { kind?: undefined } & ConstraintSystemTestBase; +type Not = { kind: 'not' } & ConstraintSystemTestBase; +type And = { kind: 'and'; tests: ConstraintSystemTest[]; label: string }; +type Or = { kind: 'or'; tests: ConstraintSystemTest[]; label: string }; +type ConstraintSystemTest = Base | Not | And | Or; type Result = { ok: boolean; failures: string[] }; -function run(test: CsTest, cs: Gate[], inputs: TypeAndValue[]): Result { +function run( + test: ConstraintSystemTest, + cs: Gate[], + inputs: TypeAndValue[] +): Result { switch (test.kind) { case undefined: { let ok = test.run(cs, inputs); @@ -124,26 +129,26 @@ function run(test: CsTest, cs: Gate[], inputs: TypeAndValue[]): Result { /** * Negate a test. */ -function not(test: CsTest): CsTest { +function not(test: ConstraintSystemTest): ConstraintSystemTest { return { kind: 'not', ...test }; } /** * Check that all input tests pass. */ -function and(...tests: CsTest[]): CsTest { +function and(...tests: ConstraintSystemTest[]): ConstraintSystemTest { return { kind: 'and', tests, label: `and(${tests.map((t) => t.label)})` }; } /** * Check that at least one input test passes. */ -function or(...tests: CsTest[]): CsTest { +function or(...tests: ConstraintSystemTest[]): ConstraintSystemTest { return { kind: 'or', tests, label: `or(${tests.map((t) => t.label)})` }; } /** * Test for precise equality of the constraint system with a given list of gates. */ -function equals(gates: readonly GateType[]): CsTest { +function equals(gates: readonly GateType[]): ConstraintSystemTest { return { run(cs) { if (cs.length !== gates.length) return false; @@ -173,7 +178,7 @@ function equals(gates: readonly GateType[]): CsTest { */ function contains( gates: GateType | readonly GateType[] | readonly GateType[][] -): CsTest { +): ConstraintSystemTest { let expectedGatess = toGatess(gates); return { run(cs) { @@ -203,7 +208,7 @@ function contains( /** * Test whether all inputs are constant. */ -const allConstant: CsTest = { +const allConstant: ConstraintSystemTest = { run(cs, inputs) { return inputs.every(({ type, value }) => type.toFields(value).every((x) => x.isConstant()) @@ -216,14 +221,14 @@ const allConstant: CsTest = { * Modifies a test so that it doesn't fail if all inputs are constant, and instead * checks that the constraint system is empty in that case. */ -function ifNotAllConstant(test: CsTest): CsTest { +function ifNotAllConstant(test: ConstraintSystemTest): ConstraintSystemTest { return or(test, and(allConstant, isEmpty)); } /** * Test whether all inputs are constant. */ -const isEmpty: CsTest = { +const isEmpty: ConstraintSystemTest = { run(cs) { return cs.length === 0; }, @@ -233,7 +238,7 @@ const isEmpty: CsTest = { /** * Modifies a test so that it runs on the constraint system with generic gates filtered out. */ -function withoutGenerics(test: CsTest): CsTest { +function withoutGenerics(test: ConstraintSystemTest): ConstraintSystemTest { return { run(cs, inputs) { return run( @@ -249,7 +254,7 @@ function withoutGenerics(test: CsTest): CsTest { /** * "Test" that just logs the constraint system. */ -const print: CsTest = { +const print: ConstraintSystemTest = { run(cs) { console.log('Constraint system:'); printGates(cs); From 3e0fabbe0834436d1ba9c79adde7f72542fd9595 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:20:29 +0100 Subject: [PATCH 0622/1215] document test runner --- src/lib/testing/constraint-system.ts | 54 ++++++++++++++++++---------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index aafe87e38d..91ff84558b 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -28,24 +28,27 @@ export { ConstraintSystemTest, }; -type CsVarSpec = Provable | { provable: Provable }; -type InferCsVar = T extends { provable: Provable } - ? U - : T extends Provable - ? U - : never; -type CsParams>> = { - [k in keyof In]: InferCsVar; -}; -type TypeAndValue = { type: Provable; value: T }; - -// main DSL - -function constraintSystem>>( +/** + * `constraintSystem()` is a test runner to check properties of constraint systems. + * You give it a description of inputs and a circuit, as well as a `ConstraintSystemTest` to assert + * properties on the generated constraint system. + * + * As inputs variables, we generate random combinations of constants, variables, add & scale combinators, + * to poke for the common problem of gate chains broken by unexpected Generic gates. + * + * The `constraintSystemTest` is written using a DSL of property assertions, such as {@link equals} and {@link contains}. + * To run multiple assertions, use the {@link and} / {@link or} combinators. + * + * @param label description of the constraint system + * @param inputs input spec in form `{ from: [...provables] }` + * @param main circuit to test + * @param constraintSystemTest property test to run on the constraint system + */ +function constraintSystem>>( label: string, - inputs: { from: In }, - main: (...args: CsParams) => void, - csTest: ConstraintSystemTest + inputs: { from: Input }, + main: (...args: CsParams) => void, + constraintSystemTest: ConstraintSystemTest ) { // create random generators let types = inputs.from.map(provable); @@ -59,14 +62,14 @@ function constraintSystem>>( // each random input "layout" has to be instantiated into vars in this circuit let values = types.map((type, i) => instantiate(type, layouts[i]) - ) as CsParams; + ) as CsParams; main(...values); }); // run tests let typesAndValues = types.map((type, i) => ({ type, value: layouts[i] })); - let { ok, failures } = run(csTest, gates, typesAndValues); + let { ok, failures } = run(constraintSystemTest, gates, typesAndValues); if (!ok) { console.log('Constraint system:'); @@ -347,6 +350,19 @@ function drawFieldType(): FieldType { return FieldType.Add; } +// types + +type CsVarSpec = Provable | { provable: Provable }; +type InferCsVar = T extends { provable: Provable } + ? U + : T extends Provable + ? U + : never; +type CsParams>> = { + [k in keyof In]: InferCsVar; +}; +type TypeAndValue = { type: Provable; value: T }; + // print a constraint system function printGates(gates: Gate[]) { From 015d29f7c682721fd673b95985956e8588a91549 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:24:30 +0100 Subject: [PATCH 0623/1215] changelog --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ff71d070..c328c4c0ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,15 +19,17 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) -> No unreleased changes yet +### Added + +- Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 ## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) ### Added -- `Gadgets.not()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1198 -- `Gadgets.leftShift() / Gadgets.rightShift()`, new provable method to support bitwise shifting for native field elements. https://github.com/o1-labs/o1js/pull/1194 -- `Gadgets.and()`, new provable method to support bitwise and for native field elements. https://github.com/o1-labs/o1js/pull/1193 +- `Gadgets.not()`, new provable method to support bitwise not. https://github.com/o1-labs/o1js/pull/1198 +- `Gadgets.leftShift() / Gadgets.rightShift()`, new provable methods to support bitwise shifting. https://github.com/o1-labs/o1js/pull/1194 +- `Gadgets.and()`, new provable method to support bitwise and. https://github.com/o1-labs/o1js/pull/1193 - `Gadgets.multiRangeCheck()` and `Gadgets.compactMultiRangeCheck()`, two building blocks for non-native arithmetic with bigints of size up to 264 bits. https://github.com/o1-labs/o1js/pull/1216 ## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) From 5883f5e3e89f2f87308ed958c1d0f49ff2864733 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:28:20 +0100 Subject: [PATCH 0624/1215] dump vks --- tests/vk-regression/vk-regression.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index b702555394..03974dc499 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -178,11 +178,11 @@ }, "notUnchecked": { "rows": 2, - "digest": "fa18d403c061ef2be221baeae18ca19d" + "digest": "bc6dd8d1aa3f7fe3718289fe4a07f81d" }, "notChecked": { "rows": 17, - "digest": "5e01b2cad70489c7bec1546b84ac868d" + "digest": "b12ad7e8a3fd28b765e059357dbe9e44" }, "leftShift": { "rows": 7, From bd358777499f3764655236b839a6162de7a3496f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:28:28 +0100 Subject: [PATCH 0625/1215] tweak comments --- src/lib/testing/constraint-system.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 91ff84558b..0c19d04215 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -38,6 +38,7 @@ export { * * The `constraintSystemTest` is written using a DSL of property assertions, such as {@link equals} and {@link contains}. * To run multiple assertions, use the {@link and} / {@link or} combinators. + * To debug the constraint system, use the {@link print} test or `and(print, ...otherTests)`. * * @param label description of the constraint system * @param inputs input spec in form `{ from: [...provables] }` @@ -255,7 +256,7 @@ function withoutGenerics(test: ConstraintSystemTest): ConstraintSystemTest { } /** - * "Test" that just logs the constraint system. + * "Test" that just pretty-prints the constraint system. */ const print: ConstraintSystemTest = { run(cs) { From 2a95ca8dd2c5c0a155c730240ce109f1b60f309a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 13 Nov 2023 17:52:45 +0100 Subject: [PATCH 0626/1215] more changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c328c4c0ce..18c35fec7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 +### Changed + +- Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 + ## [0.14.1](https://github.com/o1-labs/o1js/compare/e8e7510e1...26363465d) ### Added From a250c1c7ba7993af8deee2aa802b5d02372711c7 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Mon, 13 Nov 2023 23:39:02 +0100 Subject: [PATCH 0627/1215] make array spec support provable --- src/lib/testing/equivalent.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index a437dc85e9..ab1241b94b 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -252,6 +252,14 @@ function fieldWithRng(rng: Random): Spec { // spec combinators +function array( + spec: ProvableSpec, + n: number +): ProvableSpec; +function array( + spec: Spec, + n: Random | number +): Spec; function array( spec: Spec, n: Random | number @@ -260,6 +268,10 @@ function array( rng: Random.array(spec.rng, n), there: (x) => x.map(spec.there), back: (x) => x.map(spec.back), + provable: + typeof n === 'number' && spec.provable + ? Provable.Array(spec.provable, n) + : undefined, }; } From bcdbebdf4e5e108febc160fc5a52ea7f0f0fec76 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Mon, 13 Nov 2023 23:39:24 +0100 Subject: [PATCH 0628/1215] add failing cs test --- src/lib/gadgets/foreign-field.unit-test.ts | 36 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index cb198dc87e..f97e976cb6 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -1,7 +1,7 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { - Spec, + ProvableSpec, array, equivalentAsync, equivalentProvable, @@ -16,10 +16,20 @@ import { Field } from '../field.js'; import { provablePure } from '../circuit_value.js'; import { TupleN } from '../util/types.js'; import { assert } from './common.js'; +import { + and, + constraintSystem, + contains, + equals, + ifNotAllConstant, + repeat, + withoutGenerics, +} from '../testing/constraint-system.js'; +import { GateType } from '../../snarky.js'; const Field3_ = provablePure([Field, Field, Field] as TupleN); -function foreignField(F: FiniteField): Spec { +function foreignField(F: FiniteField): ProvableSpec { let rng = Random.otherField(F); return { rng, @@ -74,13 +84,29 @@ for (let F of fields) { ); } -// tests with proving +// tests for constraint system let F = exampleFields.secp256k1; let f = foreignField(F); - let chainLength = 5; -let signs = [-1n, 1n, -1n, -1n] satisfies Sign[]; +let signs = [1n, -1n, -1n, 1n] satisfies Sign[]; + +let addChain = repeat(chainLength - 1, 'ForeignFieldAdd').concat('Zero'); +let mrc: GateType[] = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; + +constraintSystem( + 'foreign field sum chain', + { from: [array(f, chainLength)] }, + (xs) => ForeignField.sumChain(xs, signs, F.modulus), + ifNotAllConstant( + and( + contains([addChain, mrc]), + withoutGenerics(equals([...addChain, ...mrc])) + ) + ) +); + +// tests with proving let ffProgram = ZkProgram({ name: 'foreign-field', From 64dc87affcfc8bfa8227fc15dd3c3590df110c9b Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Mon, 13 Nov 2023 23:43:00 +0100 Subject: [PATCH 0629/1215] fix test --- src/lib/gadgets/foreign-field.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 1d596d248b..9435114615 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -2,7 +2,7 @@ import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; -import { assert, exists } from './common.js'; +import { assert, exists, toVars } from './common.js'; import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; export { ForeignField, Field3, Sign }; @@ -44,8 +44,8 @@ function sumChain(x: Field3[], sign: Sign[], f: bigint) { let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); return ForeignField.from(mod(sum, f)); } - // provable case - create chain of ffadd rows + x = x.map(toVars); let result = x[0]; for (let i = 0; i < sign.length; i++) { ({ result } = singleAdd(result, x[i + 1], sign[i], f)); From 3e44714c234d33cfd750386dc49f959196b242d6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 13 Nov 2023 15:36:27 -0800 Subject: [PATCH 0630/1215] docs(README-dev.md): clarify bindings --- README-dev.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README-dev.md b/README-dev.md index c92ff4fc5c..0b21c150d6 100644 --- a/README-dev.md +++ b/README-dev.md @@ -37,7 +37,7 @@ This will compile the TypeScript source files, making it ready for use. The comp If you need to regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js, so you can build them from within the o1js repo. -o1js depends on OCaml code that is transplied to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [snarky](https://github.com/o1-labs/snarky) and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. +o1js depends on OCaml code that is transplied to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/o1-labs/snarkyhttps://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. The compiled artifacts are stored under `src/bindings/compiled`, and are version-controlled to simplify the build process for end-users. @@ -51,11 +51,11 @@ This will build the OCaml and Rust artifacts, and copy them to the `src/bindings ### OCaml Bindings -The OCaml bindings are located under `src/bindings`, and they specify all of the low-level OCaml code that is exposed to o1js. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. +o1js depends on Pickles, snarky, and parts of the Mina transaction logic, all of which are compiled to JavaScript and stored as artifacts to be used by o1js natively. The OCaml bindings are located under `src/bindings`. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. ### WebAssembly Bindings -The WebAssembly bindings are built using Rust's `wasm-pack`. Ensure you have it installed and configured correctly. +o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo, under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. ## Development From fb543edf3fad7c605e4cddaf6e62354e20bbb51e Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 14 Nov 2023 11:24:20 +0300 Subject: [PATCH 0631/1215] feat(int.ts): add support for bitwise XOR, NOT, rotate, leftShift, rightShift, and bitwise AND operations on UInt64 values using Gadgets module The Gadgets module provides implementations for bitwise XOR, NOT, rotate, leftShift, rightShift, and bitwise AND operations on UInt64 values. These operations are useful for performing bitwise operations on UInt64 values in a circuit. The following operations have been added: - `xor(x: UInt64)`: Performs a bitwise XOR operation between the current UInt64 value and the provided UInt64 value `x`. Returns a new UInt64 value representing the result of the XOR operation. - `not(checked = true)`: Performs a bitwise NOT operation on the current UInt64 value. If the `checked` parameter is set to `true`, the operation is implemented using the `Gadgets.xor` gadget with a second argument as an all one bitmask of the same length. If the `checked` parameter is set to `false`, the operation is implemented as a subtraction of the input from the all one bitmask. Returns a new UInt64 value representing the result of the NOT operation. - `rotate(direction: 'left' | 'right' = 'left')`: Performs a bitwise rotation operation on the current UInt64 value. The `direction` parameter determines the direction of the rotation, with `'left'` indicating a left rotation and `'right'` indicating a right rotation. Returns a new UInt64 value representing the result of the rotation operation. - `leftShift()`: Performs a bitwise left shift operation on the current UInt64 value. Returns a new UInt64 value representing the result of the left shift operation. - `rightShift()`: Performs a bitwise right shift operation on the current UInt64 value. Returns a new UInt64 value representing the result of the right shift operation. - `and(x: UInt64)`: Performs a bitwise AND operation between the current UInt64 value and the provided UInt64 value `x`. Returns a new UInt64 value representing the result of the AND operation. These operations provide additional functionality for working with UInt64 values in circuits. --- src/lib/int.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index a48118fa2c..4d33cf8832 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,6 +3,7 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -204,6 +205,71 @@ class UInt64 extends CircuitValue { return new UInt64(z); } + xor(x: UInt64) { + return Gadgets.xor(this.value, x.value, 64); + } + + /** + * Bitwise NOT gate on {@link Field} elements. Similar to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate operates over 64 bit for UInt64 types. + * + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. + * + * + * NOT is implemented in two different ways. If the `checked` parameter is set to `true` + * the {@link Gadgets.xor} gadget is reused with a second argument to be an + * all one bitmask the same length. This approach needs as many rows as an XOR would need + * for a single negation. If the `checked` parameter is set to `false`, NOT is + * implemented as a subtraction of the input from the all one bitmask. This + * implementation is returned by default if no `checked` parameter is provided. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * + * @example + * ```ts + * // NOTing 4 bits with the unchecked version + * let a = UInt64.from(0b0101); + * let b = a.not(false); + * + * b.assertEquals(0b1010); + * + * // NOTing 4 bits with the checked version utilizing the xor gadget + * let a = UInt64(0b0101); + * let b = a.not(); + * + * b.assertEquals(0b1010); + * ``` + * + * @param a - The value to apply NOT to. + * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it + * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented + * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * + */ + not(checked = true) { + return Gadgets.not(this.value, 64, checked); + } + + rotate(direction: 'left' | 'right' = 'left') { + return Gadgets.rotate(this.value, 64, direction); + } + + leftShift() { + return Gadgets.leftShift(this.value, 64); + } + + rightShift() { + return Gadgets.rightShift(this.value, 64); + } + + and(x: UInt64) { + return Gadgets.and(this.value, x.value, 64); + } /** * @deprecated Use {@link lessThanOrEqual} instead. * From 7f5e54f49908dead0c30454582295491fa06c4a6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 09:39:28 +0100 Subject: [PATCH 0632/1215] expose private input types from zkprogram & fix missing Proof --- src/lib/proof_system.ts | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 829d824f00..a4f24dbb03 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -271,6 +271,13 @@ function ZkProgram< analyzeMethods: () => ReturnType[]; publicInputType: ProvableOrUndefined>; publicOutputType: ProvableOrVoid>; + privateInputTypes: { + [I in keyof Types]: Method< + InferProvableOrUndefined>, + InferProvableOrVoid>, + Types[I] + >['privateInputs']; + }; rawMethods: { [I in keyof Types]: Method< InferProvableOrUndefined>, @@ -286,8 +293,8 @@ function ZkProgram< >; } { let methods = config.methods; - let publicInputType: ProvablePure = config.publicInput! ?? Undefined; - let publicOutputType: ProvablePure = config.publicOutput! ?? Void; + let publicInputType: ProvablePure = config.publicInput ?? Undefined; + let publicOutputType: ProvablePure = config.publicOutput ?? Void; let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< @@ -301,11 +308,11 @@ function ZkProgram< static tag = () => selfTag; } - let keys: (keyof Types & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order - let methodIntfs = keys.map((key) => + let methodKeys: (keyof Types & string)[] = Object.keys(methods).sort(); // need to have methods in (any) fixed order + let methodIntfs = methodKeys.map((key) => sortMethodArguments('program', key, methods[key].privateInputs, SelfProof) ); - let methodFunctions = keys.map((key) => methods[key].method); + let methodFunctions = methodKeys.map((key) => methods[key].method); let maxProofsVerified = getMaxProofsVerified(methodIntfs); function analyzeMethods() { @@ -394,7 +401,7 @@ function ZkProgram< } return [key, prove]; } - let provers = Object.fromEntries(keys.map(toProver)) as { + let provers = Object.fromEntries(methodKeys.map(toProver)) as { [I in keyof Types]: Prover; }; @@ -427,21 +434,34 @@ function ZkProgram< compile, verify, digest, + analyzeMethods, publicInputType: publicInputType as ProvableOrUndefined< Get >, publicOutputType: publicOutputType as ProvableOrVoid< Get >, - analyzeMethods, + privateInputTypes: Object.fromEntries( + methodKeys.map((key) => [key, methods[key].privateInputs]) + ) as any, rawMethods: Object.fromEntries( - Object.entries(methods).map(([k, v]) => [k, v.method]) + methodKeys.map((key) => [key, methods[key].method]) ) as any, }, provers ); } +type ZkProgram< + S extends { + publicInput?: FlexibleProvablePure; + publicOutput?: FlexibleProvablePure; + }, + T extends { + [I in string]: Tuple; + } +> = ReturnType>; + let i = 0; class SelfProof extends Proof< @@ -925,6 +945,7 @@ ZkProgram.Proof = function < static tag = () => program; }; }; +ExperimentalZkProgram.Proof = ZkProgram.Proof; function dummyProof(maxProofsVerified: 0 | 1 | 2, domainLog2: number) { return withThreadPool( From 4335d9c3f7f3464a74a9e8f08f2f07a721c9cf95 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 09:40:30 +0100 Subject: [PATCH 0633/1215] make it easy to do cs tests on zkprograms --- src/lib/gadgets/foreign-field.unit-test.ts | 37 +++++++++++----------- src/lib/testing/constraint-system.ts | 36 +++++++++++++++++++-- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index f97e976cb6..c39a2c457e 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -84,30 +84,13 @@ for (let F of fields) { ); } -// tests for constraint system +// setup zk program tests let F = exampleFields.secp256k1; let f = foreignField(F); let chainLength = 5; let signs = [1n, -1n, -1n, 1n] satisfies Sign[]; -let addChain = repeat(chainLength - 1, 'ForeignFieldAdd').concat('Zero'); -let mrc: GateType[] = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; - -constraintSystem( - 'foreign field sum chain', - { from: [array(f, chainLength)] }, - (xs) => ForeignField.sumChain(xs, signs, F.modulus), - ifNotAllConstant( - and( - contains([addChain, mrc]), - withoutGenerics(equals([...addChain, ...mrc])) - ) - ) -); - -// tests with proving - let ffProgram = ZkProgram({ name: 'foreign-field', publicOutput: Field3_, @@ -121,6 +104,24 @@ let ffProgram = ZkProgram({ }, }); +// tests for constraint system + +let addChain = repeat(chainLength - 1, 'ForeignFieldAdd').concat('Zero'); +let mrc: GateType[] = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; + +constraintSystem.fromZkProgram( + ffProgram, + 'sumchain', + ifNotAllConstant( + and( + contains([addChain, mrc]), + withoutGenerics(equals([...addChain, ...mrc])) + ) + ) +); + +// tests with proving + await ffProgram.compile(); await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 5 })( diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 0c19d04215..90f3ec1599 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -11,6 +11,7 @@ import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { Random } from './random.js'; import { test } from './property.js'; +import { Undefined, ZkProgram } from '../proof_system.js'; export { constraintSystem, @@ -33,7 +34,7 @@ export { * You give it a description of inputs and a circuit, as well as a `ConstraintSystemTest` to assert * properties on the generated constraint system. * - * As inputs variables, we generate random combinations of constants, variables, add & scale combinators, + * As input variables, we generate random combinations of constants, variables, add & scale combinators, * to poke for the common problem of gate chains broken by unexpected Generic gates. * * The `constraintSystemTest` is written using a DSL of property assertions, such as {@link equals} and {@link contains}. @@ -85,6 +86,37 @@ function constraintSystem>>( }); } +/** + * Convenience function to run {@link constraintSystem} on the method of a {@link ZkProgram}. + * + * @example + * ```ts + * const program = ZkProgram({ methods: { myMethod: ... }, ... }); + * + * constraintSystem.fromZkProgram(program, 'myMethod', contains('Rot64')); + * ``` + */ +constraintSystem.fromZkProgram = function fromZkProgram< + T, + K extends keyof T & string +>( + program: { privateInputTypes: T }, + methodName: K, + test: ConstraintSystemTest +) { + let program_: ZkProgram = program as any; + let from: any = [...program_.privateInputTypes[methodName]]; + if (program_.publicInputType !== Undefined) { + from.unshift(program_.publicInputType); + } + return constraintSystem( + `${program_.name} / ${methodName}()`, + { from }, + program_.rawMethods[methodName], + test + ); +}; + // DSL for writing tests type ConstraintSystemTestBase = { @@ -267,7 +299,7 @@ const print: ConstraintSystemTest = { label: '', }; -function repeat(n: number, gates: GateType | GateType[]): GateType[] { +function repeat(n: number, gates: GateType | GateType[]): readonly GateType[] { gates = Array.isArray(gates) ? gates : [gates]; return Array(n).fill(gates).flat(); } From 840fc7a271a52de8c906cf4d3dde3274e2cf2ba0 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 14 Nov 2023 11:42:50 +0300 Subject: [PATCH 0634/1215] fix(int.ts): update parameter name in rotate method from 'direction' to 'bits' to improve clarity feat(int.ts): add documentation for the rotate, leftShift, rightShift, and and methods to improve code understanding and usage --- src/lib/int.ts | 125 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 4d33cf8832..b459113321 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -205,8 +205,27 @@ class UInt64 extends CircuitValue { return new UInt64(z); } + /** + * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * This gadget builds a chain of XOR gates recursively. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * + * @param x {@link UInt64} element to compare. + * + * @example + * ```ts + * let a = UInt64.from(0b0101); + * let b = UInt64.from(0b0011); + * + * let c = a.xor(b); + * c.assertEquals(0b0110); + * ``` + */ xor(x: UInt64) { - return Gadgets.xor(this.value, x.value, 64); + return Gadgets.xor(this.value, x.value, UInt64.NUM_BITS); } /** @@ -252,24 +271,112 @@ class UInt64 extends CircuitValue { * */ not(checked = true) { - return Gadgets.not(this.value, 64, checked); + return Gadgets.not(this.value, UInt64.NUM_BITS, checked); } - rotate(direction: 'left' | 'right' = 'left') { - return Gadgets.rotate(this.value, 64, direction); + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * + * @param bits amount of bits to rotate this {@link UInt64} element with. + * @param direction left or right rotation direction. + * + * + * @example + * ```ts + * const x = UInt64.from(0b001100); + * const y = x.rotate(2, 'left'); + * const z = x.rotate(2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * ``` + */ + rotate(bits: number, direction: 'left' | 'right' = 'left') { + return Gadgets.rotate(this.value, bits, direction); } - leftShift() { - return Gadgets.leftShift(this.value, 64); + /** + * Performs a left shift operation on the provided {@link UInt64} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt64} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt64.from(0b001100); // 12 in binary + * const y = x.leftShift(2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * ``` + */ + leftShift(bits: number) { + return Gadgets.leftShift(this.value, bits); } - rightShift() { - return Gadgets.rightShift(this.value, 64); + /** + * Performs a left right operation on the provided {@link UInt64} element. + * This operation is similar to the `>>` shift operation in JavaScript, + * where bits are shifted to the right, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt64} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt64.from(0b001100); // 12 in binary + * const y = x.rightShift(2); // left shift by 2 bits + * y.assertEquals(0b000011); // 48 in binary + * ``` + */ + rightShift(bits: number) { + return Gadgets.rightShift(this.value, bits); } + /** + * Bitwise AND gadget on {@link UInt64} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a double generic gate that verifies the following relationship between the values below. + * + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a ^ b = xor`\ + * `a & b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * + * + * @example + * ```typescript + * let a = UInt64.from(3); // ... 000011 + * let b = UInt64.from(5); // ... 000101 + * + * let c = a.and(b, 2); // ... 000001 + * c.assertEquals(1); + * ``` + */ and(x: UInt64) { - return Gadgets.and(this.value, x.value, 64); + return Gadgets.and(this.value, x.value, UInt64.NUM_BITS); } + /** * @deprecated Use {@link lessThanOrEqual} instead. * From f6f9177e5b94ed823adde762571f4f0ff6e89e52 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 14 Nov 2023 11:45:32 +0300 Subject: [PATCH 0635/1215] fix(int.ts): fix instantiation of UInt64 in example code by using the 'from' method instead of direct instantiation to improve clarity and consistency feat(int.ts): add support for bitwise XOR operation on UInt32 elements to perform XOR operation between two UInt32 elements feat(int.ts): add support for bitwise NOT operation on UInt32 elements to perform bitwise negation of a UInt32 element feat(int.ts): add support for rotation operation on UInt32 elements to perform left and right rotation of bits in a UInt32 element feat(int.ts): add support for left shift operation on UInt32 elements to perform left shift of bits in a UInt32 element feat(int.ts): add support for right shift operation on UInt32 elements to perform right shift of bits in a UInt32 element feat(int.ts): add support for bitwise AND operation on UInt32 elements to perform AND operation between two UInt32 elements --- src/lib/int.ts | 175 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index b459113321..3c3391d99a 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -258,7 +258,7 @@ class UInt64 extends CircuitValue { * b.assertEquals(0b1010); * * // NOTing 4 bits with the checked version utilizing the xor gadget - * let a = UInt64(0b0101); + * let a = UInt64.from(0b0101); * let b = a.not(); * * b.assertEquals(0b1010); @@ -715,6 +715,179 @@ class UInt32 extends CircuitValue { z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); return new UInt32(z); } + + /** + * Bitwise XOR gadget on {@link UInt32} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). + * A XOR gate works by comparing two bits and returning `1` if two bits differ, and `0` if two bits are equal. + * + * This gadget builds a chain of XOR gates recursively. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) + * + * @param x {@link UInt32} element to compare. + * + * @example + * ```ts + * let a = UInt32.from(0b0101); + * let b = UInt32.from(0b0011); + * + * let c = a.xor(b); + * c.assertEquals(0b0110); + * ``` + */ + xor(x: UInt32) { + return Gadgets.xor(this.value, x.value, UInt32.NUM_BITS); + } + + /** + * Bitwise NOT gate on {@link UInt32} elements. Similar to the [bitwise + * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ + * Web/JavaScript/Reference/Operators/Bitwise_NOT). + * + * **Note:** The NOT gate operates over 64 bit for UInt64 types. + * + * A NOT gate works by returning `1` in each bit position if the + * corresponding bit of the operand is `0`, and returning `0` if the + * corresponding bit of the operand is `1`. + * + * + * NOT is implemented in two different ways. If the `checked` parameter is set to `true` + * the {@link Gadgets.xor} gadget is reused with a second argument to be an + * all one bitmask the same length. This approach needs as many rows as an XOR would need + * for a single negation. If the `checked` parameter is set to `false`, NOT is + * implemented as a subtraction of the input from the all one bitmask. This + * implementation is returned by default if no `checked` parameter is provided. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) + * + * @example + * ```ts + * // NOTing 4 bits with the unchecked version + * let a = UInt32.from(0b0101); + * let b = a.not(false); + * + * b.assertEquals(0b1010); + * + * // NOTing 4 bits with the checked version utilizing the xor gadget + * let a = UInt32.from(0b0101); + * let b = a.not(); + * + * b.assertEquals(0b1010); + * ``` + * + * @param a - The value to apply NOT to. + * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it + * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented + * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. + * + */ + not(checked = true) { + return Gadgets.not(this.value, UInt32.NUM_BITS, checked); + } + + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; + * for example, using {@link Gadgets.rangeCheck64}. + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) + * + * @param bits amount of bits to rotate this {@link UInt32} element with. + * @param direction left or right rotation direction. + * + * + * @example + * ```ts + * const x = UInt32.from(0b001100); + * const y = x.rotate(2, 'left'); + * const z = x.rotate(2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * ``` + */ + rotate(bits: number, direction: 'left' | 'right' = 'left') { + return Gadgets.rotate(this.value, bits, direction); + } + + /** + * Performs a left shift operation on the provided {@link UInt32} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt32} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt32.from(0b001100); // 12 in binary + * const y = x.leftShift(2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * ``` + */ + leftShift(bits: number) { + return Gadgets.leftShift(this.value, bits); + } + + /** + * Performs a left right operation on the provided {@link UInt32} element. + * This operation is similar to the `>>` shift operation in JavaScript, + * where bits are shifted to the right, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, + * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * + * @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * + * @example + * ```ts + * const x = UInt32.from(0b001100); // 12 in binary + * const y = x.rightShift(2); // left shift by 2 bits + * y.assertEquals(0b000011); // 48 in binary + * ``` + */ + rightShift(bits: number) { + return Gadgets.rightShift(this.value, bits); + } + + /** + * Bitwise AND gadget on {@link UInt32} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). + * The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise. + * + * It can be checked by a double generic gate that verifies the following relationship between the values below. + * + * The generic gate verifies:\ + * `a + b = sum` and the conjunction equation `2 * and = sum - xor`\ + * Where:\ + * `a + b = sum`\ + * `a ^ b = xor`\ + * `a & b = and` + * + * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and) + * + * + * @example + * ```typescript + * let a = UInt32.from(3); // ... 000011 + * let b = UInt32.from(5); // ... 000101 + * + * let c = a.and(b, 2); // ... 000001 + * c.assertEquals(1); + * ``` + */ + and(x: UInt32) { + return Gadgets.and(this.value, x.value, UInt32.NUM_BITS); + } + /** * @deprecated Use {@link lessThanOrEqual} instead. * From 92d0ba56e58e639a934d28e4e79a2b456b11d281 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 09:49:07 +0100 Subject: [PATCH 0636/1215] simplify bitwise cs tests --- src/lib/gadgets/bitwise.unit-test.ts | 47 ++++++++-------------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 6fa914a799..cfb2eb6f54 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -200,31 +200,27 @@ function xorChain(bits: number) { return repeat(Math.ceil(bits / 16), 'Xor16').concat('Zero'); } -constraintSystem( +constraintSystem.fromZkProgram( + Bitwise, 'xor', - { from: [Field, Field] }, - Bitwise.rawMethods.xor, ifNotAllConstant(contains(xorChain(254))) ); -constraintSystem( - 'not checked', - { from: [Field] }, - Bitwise.rawMethods.notChecked, +constraintSystem.fromZkProgram( + Bitwise, + 'notChecked', ifNotAllConstant(contains(xorChain(254))) ); -constraintSystem( - 'not unchecked', - { from: [Field] }, - Bitwise.rawMethods.notUnchecked, +constraintSystem.fromZkProgram( + Bitwise, + 'notUnchecked', ifNotAllConstant(contains('Generic')) ); -constraintSystem( +constraintSystem.fromZkProgram( + Bitwise, 'and', - { from: [Field, Field] }, - Bitwise.rawMethods.and, ifNotAllConstant(contains(xorChain(64))) ); @@ -233,23 +229,6 @@ let isJustRotate = ifNotAllConstant( and(contains(rotChain), withoutGenerics(equals(rotChain))) ); -constraintSystem( - 'rotate', - { from: [Field] }, - Bitwise.rawMethods.rot, - isJustRotate -); - -constraintSystem( - 'left shift', - { from: [Field] }, - Bitwise.rawMethods.leftShift, - isJustRotate -); - -constraintSystem( - 'right shift', - { from: [Field] }, - Bitwise.rawMethods.rightShift, - isJustRotate -); +constraintSystem.fromZkProgram(Bitwise, 'rot', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'leftShift', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'rightShift', isJustRotate); From 54bcbd81dddb4c4d8cffad59396622dc5309462a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 09:49:13 +0100 Subject: [PATCH 0637/1215] changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c35fec7d..0888d6204b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 +- Provable non-native field arithmetic: + - `Gadgets.ForeignField.{add, sub, sumchain}()` for addition and subtraction https://github.com/o1-labs/o1js/pull/1220 +- Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 https://github.com/o1-labs/o1js/pull/1220 ### Changed From 2e5ace33739d3cc051bbbb0f539a9a9b0b25c6d0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 11:00:29 +0100 Subject: [PATCH 0638/1215] more docs on foreign field namespace --- src/lib/gadgets/gadgets.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 9dc58580b8..65c6f04421 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -316,6 +316,17 @@ const Gadgets = { /** * Gadgets for foreign field operations. + * + * A _foreign field_ is a finite field different from the native field of the proof system. + * + * The `ForeignField` namespace exposes operations like modular addition and multiplication, + * which work for any finite field of size less than 2^256. + * + * Foreign field elements are represented as 3 limbs of native field elements. + * Each limb holds 88 bits of the total, in little-endian order. + * + * All `ForeignField` gadgets expect that their input limbs are constrained to the range [0, 2^88). + * Range checks on outputs are added by the gadget itself. */ ForeignField, }; From 337c6967c8240522c4850b3f1cbbfb6d273a101f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 11:13:01 +0100 Subject: [PATCH 0639/1215] put provable on Field3 --- src/bindings | 2 +- src/lib/circuit_value.ts | 2 ++ src/lib/gadgets/foreign-field.ts | 3 +++ src/lib/gadgets/foreign-field.unit-test.ts | 11 +++-------- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bindings b/src/bindings index 5df84bf1f0..a5550ba6e6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5df84bf1f06c9c1e984e19d067fcf10c8ae53299 +Subproject commit a5550ba6e6daaa7a8b61a30880d3ad25082cf786 diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 9f83d5fd02..0f4106ebb9 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -4,6 +4,7 @@ import { Field, Bool, Scalar, Group } from './core.js'; import { provable, provablePure, + provableTuple, HashInput, NonMethods, } from '../bindings/lib/provable-snarky.js'; @@ -32,6 +33,7 @@ export { // internal API export { + provableTuple, AnyConstructor, cloneCircuitValue, circuitValueEquals, diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 9435114615..94c451e817 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -1,4 +1,5 @@ import { mod } from '../../bindings/crypto/finite_field.js'; +import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; @@ -100,6 +101,8 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { function Field3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } +Field3.provable = provableTuple([Field, Field, Field]); + function bigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); } diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index c39a2c457e..20a5ca9df6 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -12,9 +12,6 @@ import { Random } from '../testing/random.js'; import { ForeignField, Field3, Sign } from './foreign-field.js'; import { ZkProgram } from '../proof_system.js'; import { Provable } from '../provable.js'; -import { Field } from '../field.js'; -import { provablePure } from '../circuit_value.js'; -import { TupleN } from '../util/types.js'; import { assert } from './common.js'; import { and, @@ -27,15 +24,13 @@ import { } from '../testing/constraint-system.js'; import { GateType } from '../../snarky.js'; -const Field3_ = provablePure([Field, Field, Field] as TupleN); - function foreignField(F: FiniteField): ProvableSpec { let rng = Random.otherField(F); return { rng, there: ForeignField.from, back: ForeignField.toBigint, - provable: Field3_, + provable: Field3.provable, }; } let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); @@ -93,10 +88,10 @@ let signs = [1n, -1n, -1n, 1n] satisfies Sign[]; let ffProgram = ZkProgram({ name: 'foreign-field', - publicOutput: Field3_, + publicOutput: Field3.provable, methods: { sumchain: { - privateInputs: [Provable.Array(Field3_, chainLength)], + privateInputs: [Provable.Array(Field3.provable, chainLength)], method(xs) { return ForeignField.sumChain(xs, signs, F.modulus); }, From ff98763aa84a3f932ae6c1c6a6daa12cc1d85f5c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 11:39:54 +0100 Subject: [PATCH 0640/1215] add doccomments for addition --- src/lib/gadgets/foreign-field.ts | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 94c451e817..adcc6c88ca 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -2,6 +2,7 @@ import { mod } from '../../bindings/crypto/finite_field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; +import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { assert, exists, toVars } from './common.js'; import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; @@ -14,6 +15,37 @@ type Sign = -1n | 1n; const ForeignField = { // arithmetic + + /** + * Foreign field addition: `x + y mod f` + * + * The modulus `f` does not need to be prime. + * + * Inputs and outputs are 3-tuples of native Fields. + * Each input limb is assumed to be in the range [0, 2^88), and the gadget is invalid if this is not the case. + * The result limbs are guaranteed to be in the same range. + * + * Note: + * + * @example + * ```ts + * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(9n)); + * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); + * + * let z = ForeignField.add(x, y, 17n); + * + * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = 9 + 10 mod 17 + * ``` + * + * **Warning**: The gadget does not assume that inputs are reduced modulo f, + * and does not prove that the result is reduced modulo f. + * It only guarantees that the result is in the correct residue class. + * + * @param x left summand + * @param y right summand + * @param f modulus + * @returns x + y mod f + */ add(x: Field3, y: Field3, f: bigint) { return sumChain([x, y], [1n], f); }, @@ -29,6 +61,10 @@ const ForeignField = { toBigint(x: Field3): bigint { return collapse(bigint3(x)); }, + /** + * Provable interface for `Field3 = [Field, Field, Field]` + */ + provable: provableTuple([Field, Field, Field]), }; /** @@ -101,7 +137,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { function Field3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } -Field3.provable = provableTuple([Field, Field, Field]); +Field3.provable = ForeignField.provable; function bigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); From df5f7dfeb22eef2334cad999a344da372ddaf165 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 12:09:55 +0100 Subject: [PATCH 0641/1215] rename sumchain to sum and add docs --- src/lib/gadgets/foreign-field.ts | 40 ++-------------- src/lib/gadgets/gadgets.ts | 80 +++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index adcc6c88ca..ed3c5b5fea 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -14,45 +14,13 @@ type bigint3 = [bigint, bigint, bigint]; type Sign = -1n | 1n; const ForeignField = { - // arithmetic - - /** - * Foreign field addition: `x + y mod f` - * - * The modulus `f` does not need to be prime. - * - * Inputs and outputs are 3-tuples of native Fields. - * Each input limb is assumed to be in the range [0, 2^88), and the gadget is invalid if this is not the case. - * The result limbs are guaranteed to be in the same range. - * - * Note: - * - * @example - * ```ts - * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(9n)); - * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); - * - * let z = ForeignField.add(x, y, 17n); - * - * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = 9 + 10 mod 17 - * ``` - * - * **Warning**: The gadget does not assume that inputs are reduced modulo f, - * and does not prove that the result is reduced modulo f. - * It only guarantees that the result is in the correct residue class. - * - * @param x left summand - * @param y right summand - * @param f modulus - * @returns x + y mod f - */ add(x: Field3, y: Field3, f: bigint) { - return sumChain([x, y], [1n], f); + return sum([x, y], [1n], f); }, sub(x: Field3, y: Field3, f: bigint) { - return sumChain([x, y], [-1n], f); + return sum([x, y], [-1n], f); }, - sumChain, + sum, // helper methods from(x: bigint): Field3 { @@ -72,7 +40,7 @@ const ForeignField = { * * assumes that inputs are range checked, does range check on the result. */ -function sumChain(x: Field3[], sign: Sign[], f: bigint) { +function sum(x: Field3[], sign: Sign[], f: bigint) { assert(x.length === sign.length + 1, 'inputs and operators match'); // constant case diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 65c6f04421..8ec6ec663f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -8,7 +8,7 @@ import { } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; -import { ForeignField } from './foreign-field.js'; +import { ForeignField, Field3 } from './foreign-field.js'; export { Gadgets }; @@ -328,5 +328,81 @@ const Gadgets = { * All `ForeignField` gadgets expect that their input limbs are constrained to the range [0, 2^88). * Range checks on outputs are added by the gadget itself. */ - ForeignField, + ForeignField: { + /** + * Foreign field addition: `x + y mod f` + * + * The modulus `f` does not need to be prime. + * + * Inputs and outputs are 3-tuples of native Fields. + * Each input limb is assumed to be in the range [0, 2^88), and the gadget is invalid if this is not the case. + * The result limbs are guaranteed to be in the same range. + * + * @example + * ```ts + * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(9n)); + * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); + * + * let z = ForeignField.add(x, y, 17n); + * + * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = 9 + 10 mod 17 + * ``` + * + * **Warning**: The gadget does not assume that inputs are reduced modulo f, + * and does not prove that the result is reduced modulo f. + * It only guarantees that the result is in the correct residue class. + * + * @param x left summand + * @param y right summand + * @param f modulus + * @returns x + y mod f + */ + add(x: Field3, y: Field3, f: bigint) { + return ForeignField.add(x, y, f); + }, + + /** + * Foreign field subtraction: `x - y mod f` + * + * See {@link ForeignField.add} for assumptions and usage examples. + * + * @param x left summand + * @param y right summand + * @param f modulus + * @returns x - y mod f + */ + sub(x: Field3, y: Field3, f: bigint) { + return ForeignField.sub(x, y, f); + }, + + /** + * Foreign field sum: `x[0] + sign[0] * x[1] + ... + sign[n-1] * x[n] mod f` + * + * This gadget takes a list of inputs and a list of signs (of size one less than the inputs), + * and computes a chain of additions or subtractions, depending on the sign. + * A sign is of type `1n | -1n`, where `1n` represents addition and `-1n` represents subtraction. + * + * See {@link ForeignField.add} for assumptions on inputs. + * + * @example + * ```ts + * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(4n)); + * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(5n)); + * let z = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); + * + * // compute x + y - z mod 17 + * let sum = ForeignField.sum([x, y, z], [1n, -1n], 17n); + * + * Provable.log(sum); // ['16', '0', '0'] = limb representation of 16 = 4 + 5 - 10 mod 17 + * ``` + * + * @param xs summands + * @param signs + * @param f modulus + * @returns the sum modulo f + */ + sum(xs: Field3[], signs: (1n | -1n)[], f: bigint) { + return ForeignField.sum(xs, signs, f); + }, + }, }; From 255ad10bbe21872a09d92c2c7a77afa2233f74a0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 12:33:26 +0100 Subject: [PATCH 0642/1215] move non-provable helpers off of foreignfield namespace --- src/lib/gadgets/foreign-field.ts | 48 ++++++++++++++++++++------------ src/lib/gadgets/gadgets.ts | 24 ++++++++++++---- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index ed3c5b5fea..00c6556876 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -2,13 +2,15 @@ import { mod } from '../../bindings/crypto/finite_field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { assert, exists, toVars } from './common.js'; import { L, lMask, multiRangeCheck, twoL, twoLMask } from './range-check.js'; export { ForeignField, Field3, Sign }; +/** + * A 3-tuple of Fields, representing a 3-limb bigint. + */ type Field3 = [Field, Field, Field]; type bigint3 = [bigint, bigint, bigint]; type Sign = -1n | 1n; @@ -21,18 +23,6 @@ const ForeignField = { return sum([x, y], [-1n], f); }, sum, - - // helper methods - from(x: bigint): Field3 { - return Field3(split(x)); - }, - toBigint(x: Field3): bigint { - return collapse(bigint3(x)); - }, - /** - * Provable interface for `Field3 = [Field, Field, Field]` - */ - provable: provableTuple([Field, Field, Field]), }; /** @@ -45,9 +35,9 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { // constant case if (x.every((x) => x.every((x) => x.isConstant()))) { - let xBig = x.map(ForeignField.toBigint); + let xBig = x.map(Field3.toBigint); let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); - return ForeignField.from(mod(sum, f)); + return Field3.from(mod(sum, f)); } // provable case - create chain of ffadd rows x = x.map(toVars); @@ -102,11 +92,33 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { return { result: [r0, r1, r2] satisfies Field3, overflow }; } -function Field3(x: bigint3): Field3 { +const Field3 = { + /** + * Turn a bigint into a 3-tuple of Fields + */ + from(x: bigint): Field3 { + return toField3(split(x)); + }, + + /** + * Turn a 3-tuple of Fields into a bigint + */ + toBigint(x: Field3): bigint { + return collapse(bigint3(x)); + }, + + /** + * Provable interface for `Field3 = [Field, Field, Field]`. + * + * Note: Witnessing this creates a plain tuple of field elements without any implicit + * range checks. + */ + provable: provableTuple([Field, Field, Field]), +}; + +function toField3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } -Field3.provable = ForeignField.provable; - function bigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 8ec6ec663f..355870ea23 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -340,8 +340,8 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(9n)); - * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); + * let x = Provable.witness(Field3.provable, () => Field3.from(9n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(10n)); * * let z = ForeignField.add(x, y, 17n); * @@ -386,9 +386,9 @@ const Gadgets = { * * @example * ```ts - * let x = Provable.witness(ForeignField.provable, () => ForeignField.from(4n)); - * let y = Provable.witness(ForeignField.provable, () => ForeignField.from(5n)); - * let z = Provable.witness(ForeignField.provable, () => ForeignField.from(10n)); + * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); + * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); * * // compute x + y - z mod 17 * let sum = ForeignField.sum([x, y, z], [1n, -1n], 17n); @@ -405,4 +405,18 @@ const Gadgets = { return ForeignField.sum(xs, signs, f); }, }, + + /** + * Helper methods to interact with 3-limb vectors of Fields. + * + * **Note:** This interface does not contain any provable methods. + */ + Field3, }; + +export namespace Gadgets { + /** + * A 3-tuple of Fields, representing a 3-limb bigint. + */ + export type Field3 = [Field, Field, Field]; +} From 6010ff1041a253d492799d124668b5363c7af7ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 12:33:33 +0100 Subject: [PATCH 0643/1215] adapt unit test --- src/lib/gadgets/foreign-field.unit-test.ts | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 20a5ca9df6..0ff87927fc 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -9,7 +9,7 @@ import { record, } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; -import { ForeignField, Field3, Sign } from './foreign-field.js'; +import { Gadgets } from './gadgets.js'; import { ZkProgram } from '../proof_system.js'; import { Provable } from '../provable.js'; import { assert } from './common.js'; @@ -24,12 +24,14 @@ import { } from '../testing/constraint-system.js'; import { GateType } from '../../snarky.js'; -function foreignField(F: FiniteField): ProvableSpec { +const { ForeignField, Field3 } = Gadgets; + +function foreignField(F: FiniteField): ProvableSpec { let rng = Random.otherField(F); return { rng, - there: ForeignField.from, - back: ForeignField.toBigint, + there: Field3.from, + back: Field3.toBigint, provable: Field3.provable, }; } @@ -57,8 +59,8 @@ for (let F of fields) { // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( - (xs, signs) => sumchain(xs, signs, F), - (xs, signs) => ForeignField.sumChain(xs, signs, F.modulus) + (xs, signs) => sum(xs, signs, F), + (xs, signs) => ForeignField.sum(xs, signs, F.modulus) ); // sumchain up to 100 @@ -68,12 +70,12 @@ for (let F of fields) { (x0, ts) => { let xs = [x0, ...ts.map((t) => t.x)]; let signs = ts.map((t) => t.sign); - return sumchain(xs, signs, F); + return sum(xs, signs, F); }, (x0, ts) => { let xs = [x0, ...ts.map((t) => t.x)]; let signs = ts.map((t) => t.sign); - return ForeignField.sumChain(xs, signs, F.modulus); + return ForeignField.sum(xs, signs, F.modulus); }, 'sumchain' ); @@ -84,7 +86,7 @@ for (let F of fields) { let F = exampleFields.secp256k1; let f = foreignField(F); let chainLength = 5; -let signs = [1n, -1n, -1n, 1n] satisfies Sign[]; +let signs = [1n, -1n, -1n, 1n] satisfies (-1n | 1n)[]; let ffProgram = ZkProgram({ name: 'foreign-field', @@ -93,7 +95,7 @@ let ffProgram = ZkProgram({ sumchain: { privateInputs: [Provable.Array(Field3.provable, chainLength)], method(xs) { - return ForeignField.sumChain(xs, signs, F.modulus); + return ForeignField.sum(xs, signs, F.modulus); }, }, }, @@ -120,7 +122,7 @@ constraintSystem.fromZkProgram( await ffProgram.compile(); await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 5 })( - (xs) => sumchain(xs, signs, F), + (xs) => sum(xs, signs, F), async (xs) => { let proof = await ffProgram.sumchain(xs); assert(await ffProgram.verify(proof), 'verifies'); @@ -131,7 +133,7 @@ await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 5 })( // helper -function sumchain(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { +function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { let sum = xs[0]; for (let i = 0; i < signs.length; i++) { sum = signs[i] === 1n ? F.add(sum, xs[i + 1]) : F.sub(sum, xs[i + 1]); From fcbeb6086f418928369e29f60fe3213ef6b02b80 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 12:41:05 +0100 Subject: [PATCH 0644/1215] refine docs --- src/lib/gadgets/gadgets.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 355870ea23..c7e1208a7f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -286,7 +286,7 @@ const Gadgets = { * * @throws Throws an error if one of the input values exceeds 88 bits. */ - multiRangeCheck(limbs: [Field, Field, Field]) { + multiRangeCheck(limbs: Field3) { multiRangeCheck(limbs); }, @@ -343,6 +343,11 @@ const Gadgets = { * let x = Provable.witness(Field3.provable, () => Field3.from(9n)); * let y = Provable.witness(Field3.provable, () => Field3.from(10n)); * + * // range check x and y + * Gadgets.multiRangeCheck(x); + * Gadgets.multiRangeCheck(y); + * + * // compute x + y mod 17 * let z = ForeignField.add(x, y, 17n); * * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = 9 + 10 mod 17 @@ -376,12 +381,15 @@ const Gadgets = { }, /** - * Foreign field sum: `x[0] + sign[0] * x[1] + ... + sign[n-1] * x[n] mod f` + * Foreign field sum: `xs[0] + signs[0] * xs[1] + ... + signs[n-1] * xs[n] mod f` * * This gadget takes a list of inputs and a list of signs (of size one less than the inputs), * and computes a chain of additions or subtractions, depending on the sign. * A sign is of type `1n | -1n`, where `1n` represents addition and `-1n` represents subtraction. * + * **Note**: For 3 or more inputs, `sum()` uses fewer constraints than a sequence of `add()` and `sub()` calls, + * because we can avoid range checks on intermediate results. + * * See {@link ForeignField.add} for assumptions on inputs. * * @example @@ -390,16 +398,16 @@ const Gadgets = { * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); * + * // range check x, y, z + * Gadgets.multiRangeCheck(x); + * Gadgets.multiRangeCheck(y); + * Gadgets.multiRangeCheck(z); + * * // compute x + y - z mod 17 * let sum = ForeignField.sum([x, y, z], [1n, -1n], 17n); * * Provable.log(sum); // ['16', '0', '0'] = limb representation of 16 = 4 + 5 - 10 mod 17 * ``` - * - * @param xs summands - * @param signs - * @param f modulus - * @returns the sum modulo f */ sum(xs: Field3[], signs: (1n | -1n)[], f: bigint) { return ForeignField.sum(xs, signs, f); From aa8c8f45342cedbbaa6a54e5d029cabf5e1c98bd Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 16:09:59 +0100 Subject: [PATCH 0645/1215] minor --- src/lib/gadgets/foreign-field.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 4a299d3f36..088a38aff8 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -131,9 +131,9 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { let r = compactMultiRangeCheck(r01, r2); // range check on q and r bounds - // TODO: this uses one RC too many.. need global RC stack + // TODO: this uses one RC too many.. need global RC stack, or get rid of bounds checks let r2Bound = weakBound(r2, f); - multiRangeCheck([q2Bound, r2Bound, toVar(0n)]); + multiRangeCheck([q2Bound, r2Bound, Field.from(0n)]); return r; } @@ -160,9 +160,8 @@ function inverse(x: Field3, f: bigint): Field3 { multiRangeCheck(xInv); // range check on q and xInv bounds // TODO: this uses one RC too many.. need global RC stack - // TODO: make sure that we can just pass non-vars to multiRangeCheck() to get rid of this let xInv2Bound = weakBound(xInv[2], f); - multiRangeCheck([q2Bound, xInv2Bound, new Field(0n)]); + multiRangeCheck([q2Bound, xInv2Bound, Field.from(0n)]); // assert r === 1 r01.assertEquals(1n); @@ -195,7 +194,7 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { multiRangeCheck(z); // range check on q and result bounds let z2Bound = weakBound(z[2], f); - multiRangeCheck([q2Bound, z2Bound, new Field(0n)]); + multiRangeCheck([q2Bound, z2Bound, Field.from(0n)]); // check that r === x // this means we don't have to range check r From 96f52da15da59586502ffbb0fba2a0ddbde9dad9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 14 Nov 2023 16:11:21 +0100 Subject: [PATCH 0646/1215] add cs tests for mul --- src/lib/gadgets/foreign-field.unit-test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index f38167ddb6..99529cdbfb 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -149,6 +149,18 @@ constraintSystem.fromZkProgram( ) ); +let mulChain: GateType[] = ['ForeignFieldMul', 'Zero']; +let mulLayout = ifNotAllConstant( + and( + contains([mulChain, mrc, mrc, mrc]), + withoutGenerics(equals([...mulChain, ...repeat(3, mrc)])) + ) +); + +constraintSystem.fromZkProgram(ffProgram, 'mul', mulLayout); +constraintSystem.fromZkProgram(ffProgram, 'inv', mulLayout); +constraintSystem.fromZkProgram(ffProgram, 'div', mulLayout); + // tests with proving await ffProgram.compile(); From c59e73269dd3cda7bb54062c5f824952642b2ba5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 12:53:26 -0800 Subject: [PATCH 0647/1215] chore: update bindings and mina submodules --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 3f2ccd8757..6529e3d742 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3f2ccd875744a2be0fe8e99e84a33b16a3ee3198 +Subproject commit 6529e3d742da58f17574db3c267c340bf35e1fb4 diff --git a/src/mina b/src/mina index 28aef4fefa..587a888ada 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 28aef4fefa0e23e107471b96053e6364c23f6d4e +Subproject commit 587a888ada0dadd92092b7f2616cf015a5305e56 From 5c67b05b2921b7e6e18acfd78141faa732fc9b3e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 12:55:13 -0800 Subject: [PATCH 0648/1215] refactor(run-jest-tests.sh): remove condition excluding 'src/mina' from jest tests This change was made because the 'snarkyjs' inside the mina repo has been removed, making the condition unnecessary. --- run-jest-tests.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/run-jest-tests.sh b/run-jest-tests.sh index f034d80158..b15b30d311 100755 --- a/run-jest-tests.sh +++ b/run-jest-tests.sh @@ -3,8 +3,5 @@ set -e shopt -s globstar # to expand '**' into nested directories for f in ./src/**/*.test.ts; do - # TODO: Remove this once we remove the `snarkyjs` inside the mina repo - if [[ $f != *"src/mina"* ]]; then - NODE_OPTIONS=--experimental-vm-modules npx jest $f; - fi + NODE_OPTIONS=--experimental-vm-modules npx jest $f; done \ No newline at end of file From c4b8ab4b771e7844c50d289f58a00d3d1b03f77c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 13:40:34 -0800 Subject: [PATCH 0649/1215] chore(bindings): update subproject commit hash to a702f052 for latest changes in the subproject --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 6529e3d742..a702f05293 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 6529e3d742da58f17574db3c267c340bf35e1fb4 +Subproject commit a702f052937604fc53997b7d4026af800a126bd3 From 879a593aeda741e8341e87145aca40f18482e67d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 13:40:47 -0800 Subject: [PATCH 0650/1215] refactor: rename npm script 'make' to 'build:bindings' in package.json and README-dev.md for better clarity and consistency --- README-dev.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README-dev.md b/README-dev.md index 0b21c150d6..a972960fe2 100644 --- a/README-dev.md +++ b/README-dev.md @@ -44,7 +44,7 @@ The compiled artifacts are stored under `src/bindings/compiled`, and are version If you wish to rebuild the OCaml and Rust artifacts, you must be able to build the Mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. Once you have configured your environment to build Mina, you can build the bindings: ```sh -npm run make +npm run build:bindings ``` This will build the OCaml and Rust artifacts, and copy them to the `src/bindings/compiled` directory. diff --git a/package.json b/package.json index 7f557a2d49..bb31bc417d 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,10 @@ "scripts": { "type-check": "tsc --noEmit", "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", - "make": "./src/bindings/scripts/update-snarkyjs-bindings.sh", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", + "build:bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", From ee48e01bcb646777e5a702db74e937516ce1b08d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 13:56:09 -0800 Subject: [PATCH 0651/1215] chore(package-lock): update --- package-lock.json | 7587 +++++++++++++++++++++------------------------ 1 file changed, 3561 insertions(+), 4026 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93ea97ca43..37beb65ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,13 +47,22 @@ "node": ">=16.4.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { @@ -61,47 +70,119 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz", - "integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -111,209 +192,289 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.13", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -470,12 +631,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -485,33 +646,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -519,14 +680,23 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -539,2531 +709,2180 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", - "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", + "node_modules/@esbuild/android-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", + "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", "cpu": [ - "arm64" + "arm" ], "dev": true, "optional": true, "os": [ - "darwin" + "android" ], "engines": { "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", + "node_modules/@esbuild/android-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", + "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "node_modules/@esbuild/android-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", + "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", + "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 4" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", + "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", + "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10.10.0" + "node": ">=12" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.1.tgz", - "integrity": "sha512-Tw5kUUOKmXGQDmQ9TSgTraFFS7HMC1HG/B7y0AN2G2UzjdAXz9BzK2rmNpCSDl7g7y0Gf/VLBm//blonvhtOTQ==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils/node_modules/jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect/node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", + "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@esbuild/linux-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", + "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", + "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", + "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", + "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", + "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "cpu": [ + "mips64el" + ], "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", + "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", + "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", + "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", + "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", + "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", + "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/types/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", + "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=12" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", + "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "node_modules/@eslint/js": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, "engines": { - "node": ">=6.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=6.0.0" + "node": ">=10.10.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "engines": { - "node": ">=6.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "engines": { - "node": ">= 8" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/@playwright/test": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.2.tgz", - "integrity": "sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.25.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.28", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "dependencies": { - "@babel/types": "^7.0.0" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@types/babel__traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", - "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" + "engines": { + "node": ">=8" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", "dev": true, "dependencies": { - "@types/node": "*" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@types/isomorphic-fetch": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", - "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "@types/istanbul-lib-report": "*" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@types/jest": { - "version": "27.0.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.3.tgz", - "integrity": "sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg==", + "node_modules/@jest/console/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", - "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", - "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "node_modules/@jest/console/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "node_modules/@jest/console/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "typescript": { + "node-notifier": { "optional": true } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, "dependencies": { - "tslib": "^1.8.1" + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" + "jest-get-type": "^29.6.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", + "node_modules/@jest/expect/node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" + "jest-get-type": "^28.0.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", + "node_modules/@jest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", + "node_modules/@jest/expect/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/@jest/expect/node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/@jest/expect/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "dependencies": { - "tslib": "^1.8.1" + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", + "node_modules/@jest/expect/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "node_modules/@jest/expect/node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=0.4.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@jest/expect/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@jest/expect/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@jest/expect/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": ">=6" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@jest/expect/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", - "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", - "dev": true - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=4" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/@jest/fake-timers/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-jest": { + "node_modules/@jest/reporters": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", "dev": true, "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/reporters/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/reporters/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/reporters/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@sinclair/typebox": "^0.24.1" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-plugin-jest-hoist": { + "node_modules/@jest/test-result": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", "dev": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", "dev": true, "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/babel-preset-jest": { + "node_modules/@jest/transform": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "bin": { - "browserslist": "cli.js" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=6.0.0" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, "engines": { - "node": ">= 6" + "node": ">=6.0.0" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "dependencies": { - "node-int64": "^0.4.0" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "engines": { - "node": ">=6" + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, "engines": { - "node": ">=6" + "node": ">= 8" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "engines": { - "node": ">=6" + "node": ">= 8" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001384", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz", - "integrity": "sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", "dev": true, + "dependencies": { + "playwright": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": ">=10" + "node": ">=16" } }, - "node_modules/ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "type-detect": "4.0.8" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "dependencies": { + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@types/babel__core": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", + "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "node_modules/@types/babel__generator": { + "version": "7.6.7", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", + "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.1" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@types/babel__traverse": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "@babel/types": "^7.20.7" } }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@types/node": "*" } }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "node_modules/@types/isomorphic-fetch": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", + "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", "dev": true }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-gpu": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.5.tgz", - "integrity": "sha512-gKBKx8mj0Fi/8DnjuzxU+aNYF1yWvPxtiOV9o72539wW0HP3ZTJVol/0FUasvdxIY1Bhi19SwIINqXGO+RB/sA==", "dependencies": { - "webgl-constants": "^1.1.1" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", + "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", "dev": true, "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "undici-types": "~5.26.4" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", + "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", "dev": true, "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/electron-to-chromium": { - "version": "1.4.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz", - "integrity": "sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { - "ansi-colors": "^4.1.1" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=8.6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/esbuild": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", - "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "optionalDependencies": { - "@esbuild/android-arm": "0.19.2", - "@esbuild/android-arm64": "0.19.2", - "@esbuild/android-x64": "0.19.2", - "@esbuild/darwin-arm64": "0.19.2", - "@esbuild/darwin-x64": "0.19.2", - "@esbuild/freebsd-arm64": "0.19.2", - "@esbuild/freebsd-x64": "0.19.2", - "@esbuild/linux-arm": "0.19.2", - "@esbuild/linux-arm64": "0.19.2", - "@esbuild/linux-ia32": "0.19.2", - "@esbuild/linux-loong64": "0.19.2", - "@esbuild/linux-mips64el": "0.19.2", - "@esbuild/linux-ppc64": "0.19.2", - "@esbuild/linux-riscv64": "0.19.2", - "@esbuild/linux-s390x": "0.19.2", - "@esbuild/linux-x64": "0.19.2", - "@esbuild/netbsd-x64": "0.19.2", - "@esbuild/openbsd-x64": "0.19.2", - "@esbuild/sunos-x64": "0.19.2", - "@esbuild/win32-arm64": "0.19.2", - "@esbuild/win32-ia32": "0.19.2", - "@esbuild/win32-x64": "0.19.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { - "node": ">=8.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=10" + "node": ">=0.4.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", "dev": true }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 8" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "node_modules/babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, "engines": { - "node": ">= 4" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "dependencies": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "fill-range": "^7.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "browserslist": "cli.js" }, "engines": { - "node": ">=4" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "dependencies": { - "estraverse": "^5.1.0" + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" + "node": ">= 6" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" + "node-int64": "^0.4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/caniuse-lite": { + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "engines": { - "node": ">= 0.8.0" + "node": ">=10" } }, - "node_modules/expect": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.0.1.tgz", - "integrity": "sha512-yQgemsjLU+1S8t2A7pXT3Sn/v5/37LY8J+tocWtKEA0iEYYc6gfKbbJJX2fxHZmd7K9WpdbQqXUpmYkq1aewYg==", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.0.1", - "jest-get-type": "^29.0.0", - "jest-matcher-utils": "^29.0.1", - "jest-message-util": "^29.0.1", - "jest-util": "^29.0.1" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/expect/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/expect/node_modules/color-convert": { + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3075,1240 +2894,1233 @@ "node": ">=7.0.0" } }, - "node_modules/expect/node_modules/color-name": { + "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/expect/node_modules/diff-sequences": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", - "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8" } }, - "node_modules/expect/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/expect/node_modules/jest-diff": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.1.tgz", - "integrity": "sha512-l8PYeq2VhcdxG9tl5cU78ClAlg/N7RtVSp0v3MlXURR0Y99i6eFnegmasOandyTmO6uEdo20+FByAjBFEO9nuw==", + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-gpu": { + "version": "5.0.37", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.37.tgz", + "integrity": "sha512-EraWs84faI4iskB4qvE39bevMIazEvd1RpoyGLOBesRLbiz6eMeJqqRPHjEFClfRByYZzi9IzU35rBXIO76oDw==", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.0.0", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" - }, + "webgl-constants": "^1.1.1" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/expect/node_modules/jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/expect/node_modules/jest-matcher-utils": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.1.tgz", - "integrity": "sha512-/e6UbCDmprRQFnl7+uBKqn4G22c/OmwriE5KCMVqxhElKCQUDcFnq5XM9iJeKtzy4DUjxT27y9VHmKPD8BQPaw==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.0.1", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.0.1" + "path-type": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/expect/node_modules/jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "esutils": "^2.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.0.0" } }, - "node_modules/expect/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "node_modules/electron-to-chromium": { + "version": "1.4.583", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.583.tgz", + "integrity": "sha512-93y1gcONABZ7uqYe/JWDVQP/Pj/sQSunF0HVAPdlg/pfBnOyBMLlQUxWvkqcljJg1+W6cjvPuYD+r1Th9Tn8mA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/expect/node_modules/pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", + "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.5", + "@esbuild/android-arm64": "0.19.5", + "@esbuild/android-x64": "0.19.5", + "@esbuild/darwin-arm64": "0.19.5", + "@esbuild/darwin-x64": "0.19.5", + "@esbuild/freebsd-arm64": "0.19.5", + "@esbuild/freebsd-x64": "0.19.5", + "@esbuild/linux-arm": "0.19.5", + "@esbuild/linux-arm64": "0.19.5", + "@esbuild/linux-ia32": "0.19.5", + "@esbuild/linux-loong64": "0.19.5", + "@esbuild/linux-mips64el": "0.19.5", + "@esbuild/linux-ppc64": "0.19.5", + "@esbuild/linux-riscv64": "0.19.5", + "@esbuild/linux-s390x": "0.19.5", + "@esbuild/linux-x64": "0.19.5", + "@esbuild/netbsd-x64": "0.19.5", + "@esbuild/openbsd-x64": "0.19.5", + "@esbuild/sunos-x64": "0.19.5", + "@esbuild/win32-arm64": "0.19.5", + "@esbuild/win32-ia32": "0.19.5", + "@esbuild/win32-x64": "0.19.5" } }, - "node_modules/expect/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/expect/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "engines": { + "node": ">=4.0" } }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "bser": "2.1.1" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=4" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "estraverse": "^5.2.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=4.0" } }, - "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": ">=4.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=4.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { - "node": ">=6.9.0" + "node": ">=0.10.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { - "node": ">=8.0.0" + "node": ">= 0.8.0" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "node_modules/expect/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/expect/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, "engines": { - "node": ">=10.13.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/expect/node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "node_modules/expect/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/expect/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "node_modules/expect/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "node_modules/expect/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": ">=8.6.0" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "function-bind": "^1.1.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">= 6" } }, - "node_modules/howslow": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/howslow/-/howslow-0.1.0.tgz", - "integrity": "sha512-AD1ERdUseZEi/XyLBa/9LNv4l4GvCCkNT76KpYp0YEv1up8Ow/ZzLy71OtlSdv6b39wxvzJKq9VZLH/83PrQkQ==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, - "engines": { - "node": ">=10.17.0" + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "engines": { - "node": ">= 4" + "dependencies": { + "bser": "2.1.1" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=0.8.19" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "dependencies": { - "has": "^1.0.3" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=12" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "engines": { - "node": ">=0.12.0" + "node": ">=8.0.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node_modules/isomorphic-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/isomorphic-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/isomorphic-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/isomorphic-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, "bin": { - "jest": "bin/jest.js" + "handlebars": "bin/handlebars" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=0.4.7" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=8" } }, - "node_modules/jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "function-bind": "^1.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "node_modules/howslow": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/howslow/-/howslow-0.1.0.tgz", + "integrity": "sha512-AD1ERdUseZEi/XyLBa/9LNv4l4GvCCkNT76KpYp0YEv1up8Ow/ZzLy71OtlSdv6b39wxvzJKq9VZLH/83PrQkQ==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": ">=10.17.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 4" } }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.8.19" } }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "hasown": "^2.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6" } }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "node_modules/jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-config/node_modules/pretty-format": { + "node_modules/jest-circus": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { + "node_modules/jest-circus/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", @@ -4320,217 +4132,267 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-circus/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-circus/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-circus/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "detect-newline": "^3.0.0" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each": { + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-cli": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", "dev": true, "dependencies": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", "@jest/types": "^28.1.3", "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-each/node_modules/@jest/schemas": { + "node_modules/jest-cli/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-config/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "node_modules/jest-config/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/pretty-format": { + "node_modules/jest-config/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", @@ -4545,54 +4407,28 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/react-is": { + "node_modules/jest-config/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-get-type": { + "node_modules/jest-diff/node_modules/jest-get-type": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", @@ -4601,57 +4437,35 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "node_modules/jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", "dev": true, "dependencies": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "detect-newline": "^3.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" } }, - "node_modules/jest-leak-detector": { + "node_modules/jest-each": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", "dev": true, "dependencies": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", "pretty-format": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-leak-detector/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "node_modules/jest-each/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", @@ -4663,7 +4477,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { + "node_modules/jest-each/node_modules/jest-get-type": { "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", @@ -4672,7 +4486,24 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { + "node_modules/jest-each/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", @@ -4687,114 +4518,103 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-leak-detector/node_modules/react-is": { + "node_modules/jest-each/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-matcher-utils": { + "node_modules/jest-environment-node": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { + "node_modules/jest-environment-node/node_modules/jest-util": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-haste-map/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-diff": { + "node_modules/jest-leak-detector": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", "jest-get-type": "^28.0.2", "pretty-format": "^28.1.3" }, @@ -4802,7 +4622,19 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/jest-get-type": { "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", @@ -4811,7 +4643,7 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "node_modules/jest-leak-detector/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", @@ -4826,151 +4658,115 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { + "node_modules/jest-leak-detector/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-message-util/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/react-is": { @@ -4979,18 +4775,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-mock": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", @@ -5005,9 +4789,9 @@ } }, "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "engines": { "node": ">=6" @@ -5063,74 +4847,21 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-runner": { @@ -5166,75 +4897,75 @@ } }, "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runner/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-runner/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-runner/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-runtime": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", @@ -5269,54 +5000,17 @@ } }, "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5337,27 +5031,64 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-runtime/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/jest-snapshot": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", @@ -5385,86 +5116,37 @@ "jest-message-util": "^28.1.3", "jest-util": "^28.1.3", "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" + "pretty-format": "^28.1.3", + "semver": "^7.3.5" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/schemas": { + "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.24.1" + "jest-get-type": "^28.0.2" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/jest-snapshot/node_modules/diff-sequences": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", @@ -5490,15 +5172,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-snapshot/node_modules/jest-diff": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", @@ -5523,67 +5196,42 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/pretty-format": { + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util": { + "node_modules/jest-snapshot/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", @@ -5600,76 +5248,79 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-util/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", @@ -5687,86 +5338,28 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jest-validate/node_modules/jest-get-type": { @@ -5793,36 +5386,12 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-watcher": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", @@ -5842,74 +5411,21 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-worker": { @@ -5926,15 +5442,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -5985,6 +5492,12 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6000,7 +5513,7 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "node_modules/json5": { @@ -6033,6 +5546,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6071,21 +5593,24 @@ "dev": true }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "node_modules/lodash.merge": { @@ -6095,15 +5620,12 @@ "dev": true }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/lunr": { @@ -6113,15 +5635,15 @@ "dev": true }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6170,13 +5692,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -6204,9 +5726,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6221,7 +5743,13 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, "node_modules/neo-async": { @@ -6230,6 +5758,25 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6237,9 +5784,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-path": { @@ -6266,7 +5813,7 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { "wrappy": "1" @@ -6288,17 +5835,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -6320,27 +5867,15 @@ } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6434,9 +5969,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -6446,9 +5981,9 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, "engines": { "node": ">= 6" @@ -6466,16 +6001,100 @@ "node": ">=8" } }, - "node_modules/playwright-core": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.2.tgz", - "integrity": "sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", "dev": true, + "dependencies": { + "playwright-core": "1.39.0" + }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/prelude-ls": { @@ -6488,9 +6107,9 @@ } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -6528,15 +6147,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -6551,9 +6161,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -6590,18 +6200,6 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/replace-in-file": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", @@ -6619,55 +6217,6 @@ "node": ">=10" } }, - "node_modules/replace-in-file/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/replace-in-file/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/replace-in-file/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/replace-in-file/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/replace-in-file/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6688,27 +6237,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/replace-in-file/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace-in-file/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6719,12 +6247,12 @@ } }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -6766,9 +6294,9 @@ } }, "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", "dev": true, "engines": { "node": ">=10" @@ -6842,21 +6370,39 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6879,9 +6425,9 @@ } }, "node_modules/shiki": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.1.tgz", - "integrity": "sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", "dev": true, "dependencies": { "ansi-sequence-parser": "^1.1.0", @@ -6937,9 +6483,9 @@ "dev": true }, "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" @@ -7027,21 +6573,21 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, "dependencies": { "has-flag": "^4.0.0", @@ -7051,27 +6597,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -7137,7 +6662,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "node_modules/tmpl": { @@ -7167,6 +6692,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-jest": { "version": "28.0.8", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", @@ -7210,25 +6740,48 @@ } } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/ts-jest/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", @@ -7252,9 +6805,9 @@ } }, "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" @@ -7285,9 +6838,9 @@ } }, "node_modules/typedoc-plugin-markdown": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.3.tgz", - "integrity": "sha512-idntFYu3vfaY3eaD+w9DeRd0PmNGqGuNLKihPU9poxFGnATJYGn9dPtEhn2QrTdishFMg7jPXAhos+2T6YCWRQ==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", "dev": true, "dependencies": { "handlebars": "^4.7.7" @@ -7297,12 +6850,12 @@ } }, "node_modules/typedoc-plugin-merge-modules": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.0.1.tgz", - "integrity": "sha512-7fiMYDUaeslsGSFDevw+azhD0dFJce0h2g5UuQ8zXljoky+YfmzoNkoTCx+KWaNJo6rz2DzaD2feVJyUhvUegg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-merge-modules/-/typedoc-plugin-merge-modules-5.1.0.tgz", + "integrity": "sha512-jXH27L/wlxFjErgBXleh3opVgjVTXFEuBo68Yfl18S9Oh/IqxK6NV94jlEJ9hl4TXc9Zm2l7Rfk41CEkcCyvFQ==", "dev": true, "peerDependencies": { - "typedoc": "0.24.x" + "typedoc": "0.24.x || 0.25.x" } }, "node_modules/typedoc/node_modules/brace-expansion": { @@ -7355,19 +6908,25 @@ "node": ">=0.8.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" } }, "node_modules/update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -7377,6 +6936,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -7384,7 +6947,7 @@ "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -7399,26 +6962,26 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -7445,10 +7008,24 @@ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } }, "node_modules/which": { "version": "2.0.2", @@ -7465,15 +7042,6 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -7497,43 +7065,10 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "node_modules/write-file-atomic": { @@ -7559,24 +7094,24 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, "node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" From 488ecb4d3843689804faab77e018c58d7a1b28b9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 13:58:50 -0800 Subject: [PATCH 0652/1215] fix(.prettierignore): update path for kimchi js files to reflect new directory structure, ensuring correct files are ignored by Prettier --- .prettierignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 099c3fecca..fe67df0c2c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,4 @@ src/bindings/compiled/web_bindings/**/plonk_wasm* src/bindings/compiled/web_bindings/**/*.bc.js src/bindings/compiled/node_bindings/* dist/**/* -src/bindings/kimchi/js/**/*.js +src/mina/src/lib/crypto/kimchi/js/**/*.js From 52d91eb437003a5f027f49530d278da9a9d504a5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 14 Nov 2023 13:59:09 -0800 Subject: [PATCH 0653/1215] chore(bindings): update subproject commit hash to 98eb83e34f2a7f99c3b5983b458dacf89211c93e --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a702f05293..98eb83e34f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a702f052937604fc53997b7d4026af800a126bd3 +Subproject commit 98eb83e34f2a7f99c3b5983b458dacf89211c93e From 3bfdc09880d4917b5cbde90a8c3602b4f619909c Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 15 Nov 2023 06:26:20 +0100 Subject: [PATCH 0654/1215] fix: add missing mrc --- src/lib/gadgets/foreign-field.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 4a299d3f36..d8e6fb7703 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -308,6 +308,9 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { negForeignFieldModulus: [f_0, f_1, f_2], }); + // multi-range check on intermediate values + multiRangeCheck([c0, p10, p110]); + return { r01, r2, q, q2Bound }; } From 67d706e16a7095d25e78b5eda17519a8a5040591 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 15 Nov 2023 09:34:48 +0100 Subject: [PATCH 0655/1215] introduce assertmul and use it for inv and div --- src/lib/gadgets/foreign-field.ts | 75 ++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index d8e6fb7703..72b5b724e8 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -153,20 +153,15 @@ function inverse(x: Field3, f: bigint): Field3 { let xInv = modInverse(Field3.toBigint(x), f); return xInv === undefined ? [0n, 0n, 0n] : split(xInv); }); - let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, xInv, f); - - // limb range checks on quotient and inverse - multiRangeCheck(q); multiRangeCheck(xInv); - // range check on q and xInv bounds - // TODO: this uses one RC too many.. need global RC stack - // TODO: make sure that we can just pass non-vars to multiRangeCheck() to get rid of this let xInv2Bound = weakBound(xInv[2], f); - multiRangeCheck([q2Bound, xInv2Bound, new Field(0n)]); - // assert r === 1 - r01.assertEquals(1n); - r2.assertEquals(0n); + let one: [Field, Field] = [Field.from(1n), Field.from(0n)]; + let q2Bound = assertMul(x, xInv, one, f); + + // range check on q and result bounds + // TODO: this uses one RC too many.. need global RC stack + multiRangeCheck([q2Bound, xInv2Bound, Field.from(0n)]); return xInv; } @@ -188,20 +183,12 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { if (yInv === undefined) return [0n, 0n, 0n]; return split(mod(Field3.toBigint(x) * yInv, f)); }); - let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(z, y, f); - - // limb range checks on quotient and result - multiRangeCheck(q); multiRangeCheck(z); - // range check on q and result bounds let z2Bound = weakBound(z[2], f); - multiRangeCheck([q2Bound, z2Bound, new Field(0n)]); + let q2Bound = assertMul(z, y, x, f); - // check that r === x - // this means we don't have to range check r - let x01 = x[0].add(x[1].mul(1n << L)); - r01.assertEquals(x01); - r2.assertEquals(x[2]); + // range check on q and result bounds + multiRangeCheck([q2Bound, z2Bound, new Field(0n)]); if (!allowZeroOverZero) { // assert that y != 0 mod f by checking that it doesn't equal 0 or f @@ -217,6 +204,48 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { return z; } +function assertMul( + x: Field3, + y: Field3, + xy: Field3 | [Field, Field], + f: bigint +) { + assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); + + // constant case + if ( + x.every((x) => x.isConstant()) && + y.every((x) => x.isConstant()) && + xy.every((x) => x.isConstant()) + ) { + let xyExpected = mod(Field3.toBigint(x) * Field3.toBigint(y), f); + let xyActual = + xy.length === 2 + ? collapse2(Tuple.map(xy, (x) => x.toBigInt())) + : Field3.toBigint(xy); + assert(xyExpected === xyActual, 'Expected xy to be x*y mod f'); + return Field.from(0n); + } + + // provable case + let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, y, f); + + // range check on quotient + multiRangeCheck(q); + + // bind remainder to input xy + if (xy.length === 2) { + let [xy01, xy2] = xy; + r01.assertEquals(xy01); + r2.assertEquals(xy2); + } else { + let xy01 = xy[0].add(xy[1].mul(1n << L)); + r01.assertEquals(xy01); + r2.assertEquals(xy[2]); + } + return q2Bound; +} + function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md let f_ = (1n << L3) - f; @@ -316,7 +345,7 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { function weakBound(x: Field, f: bigint) { let f2 = f >> L2; - let f2Bound = (1n << L) - f2 - 1n; + let f2Bound = lMask - f2; return x.add(f2Bound); } From e1009230315e462bcf4ade2cff72e988a555338d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 10:21:24 +0100 Subject: [PATCH 0656/1215] add ff tests with unreduced inputs --- src/lib/gadgets/foreign-field.unit-test.ts | 54 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 99529cdbfb..298e0b4ff7 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -28,14 +28,32 @@ import { ForeignField as ForeignField_ } from './foreign-field.js'; const { ForeignField, Field3 } = Gadgets; function foreignField(F: FiniteField): ProvableSpec { - let rng = Random.otherField(F); return { - rng, + rng: Random.otherField(F), there: Field3.from, back: Field3.toBigint, provable: Field3.provable, }; } + +// for testing with inputs > f +function unreducedForeignField( + maxBits: number, + F: FiniteField +): ProvableSpec { + return { + rng: Random.bignat(1n << BigInt(maxBits)), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + assertEqual(x, y, message) { + // need weak equality here because, while ffadd works on bigints larger than the modulus, + // it can't fully reduce them + assert(F.equal(x, y), message); + }, + }; +} + let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); let fields = [ @@ -68,6 +86,38 @@ for (let F of fields) { 'div' ); + // tests with inputs that aren't reduced mod f + 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 })( + F.add, + (x, y) => ForeignField.add(x, y, F.modulus), + 'add' + ); + // subtraction doesn't work with unreduced y because the range check on the result prevents x-y < -f + equivalentProvable({ from: [big264, f], to: big264 })( + F.sub, + (x, y) => ForeignField.sub(x, y, F.modulus), + 'sub' + ); + equivalentProvable({ from: [big258, big258], to: f })( + F.mul, + (x, y) => ForeignField_.mul(x, y, F.modulus), + 'mul' + ); + equivalentProvable({ from: [big258], to: f })( + (x) => F.inverse(x) ?? throwError('no inverse'), + (x) => ForeignField_.inv(x, F.modulus) + ); + // the div() gadget doesn't work with unreduced x because the backwards check (x/y)*y === x fails + // and it's not valid with unreduced y because we only assert y != 0, y != f but it can be 2f, 3f, etc. + // the combination of inv() and mul() is more flexible (but much more expensive, ~40 vs ~30 constraints) + equivalentProvable({ from: [big258, big258], to: f })( + (x, y) => F.div(x, y) ?? throwError('no inverse'), + (x, y) => ForeignField_.mul(x, ForeignField_.inv(y, F.modulus), F.modulus) + ); + // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( (xs, signs) => sum(xs, signs, F), From f40104c8abd478964ac9fe837bc5674ab6350c67 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 10:36:12 +0100 Subject: [PATCH 0657/1215] a bit of clean up --- src/lib/gadgets/foreign-field.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index a12b6ef461..26f6ee8687 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,9 +5,8 @@ import { import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; -import { assert, bitSlice, exists, toVars, toVar } from './common.js'; +import { assert, bitSlice, exists, toVars } from './common.js'; import { L, lMask, @@ -156,7 +155,7 @@ function inverse(x: Field3, f: bigint): Field3 { multiRangeCheck(xInv); let xInv2Bound = weakBound(xInv[2], f); - let one: [Field, Field] = [Field.from(1n), Field.from(0n)]; + let one: Field2 = [Field.from(1n), Field.from(0n)]; let q2Bound = assertMul(x, xInv, one, f); // range check on q and result bounds @@ -204,12 +203,7 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { return z; } -function assertMul( - x: Field3, - y: Field3, - xy: Field3 | [Field, Field], - f: bigint -) { +function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case @@ -219,10 +213,7 @@ function assertMul( xy.every((x) => x.isConstant()) ) { let xyExpected = mod(Field3.toBigint(x) * Field3.toBigint(y), f); - let xyActual = - xy.length === 2 - ? collapse2(Tuple.map(xy, (x) => x.toBigInt())) - : Field3.toBigint(xy); + let xyActual = xy.length === 2 ? Field2.toBigint(xy) : Field3.toBigint(xy); assert(xyExpected === xyActual, 'Expected xy to be x*y mod f'); return Field.from(0n); } @@ -354,7 +345,7 @@ const Field3 = { * Turn a bigint into a 3-tuple of Fields */ from(x: bigint): Field3 { - return toField3(split(x)); + return Tuple.map(split(x), Field.from); }, /** @@ -373,9 +364,13 @@ const Field3 = { provable: provableTuple([Field, Field, Field]), }; -function toField3(x: bigint3): Field3 { - return Tuple.map(x, (x) => new Field(x)); -} +type Field2 = [Field, Field]; +const Field2 = { + toBigint(x: Field2): bigint { + return collapse2(Tuple.map(x, (x) => x.toBigInt())); + }, +}; + function bigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); } From 4f1b21fc01abbfa65f10e212e6f406ec72442436 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 10:58:26 +0100 Subject: [PATCH 0658/1215] remove unused logic --- src/lib/gadgets/foreign-field.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 26f6ee8687..5fbade75e8 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -204,21 +204,6 @@ function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { } function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { - assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); - - // constant case - if ( - x.every((x) => x.isConstant()) && - y.every((x) => x.isConstant()) && - xy.every((x) => x.isConstant()) - ) { - let xyExpected = mod(Field3.toBigint(x) * Field3.toBigint(y), f); - let xyActual = xy.length === 2 ? Field2.toBigint(xy) : Field3.toBigint(xy); - assert(xyExpected === xyActual, 'Expected xy to be x*y mod f'); - return Field.from(0n); - } - - // provable case let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, y, f); // range check on quotient From 4532a2175814941c5c14ca25f7eef4b210c1410a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 11:46:15 +0100 Subject: [PATCH 0659/1215] add to gadgets namespace, mul doccomment --- src/lib/gadgets/gadgets.ts | 54 +++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index c7e1208a7f..79b1e39e06 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -320,7 +320,7 @@ const Gadgets = { * A _foreign field_ is a finite field different from the native field of the proof system. * * The `ForeignField` namespace exposes operations like modular addition and multiplication, - * which work for any finite field of size less than 2^256. + * which work for any finite field of size less than 2^259. * * Foreign field elements are represented as 3 limbs of native field elements. * Each limb holds 88 bits of the total, in little-endian order. @@ -412,6 +412,58 @@ const Gadgets = { sum(xs: Field3[], signs: (1n | -1n)[], f: bigint) { return ForeignField.sum(xs, signs, f); }, + + /** + * Foreign field multiplication: `x * y mod f` + * + * The modulus `f` does not need to be prime, but has to be smaller than 2^259. + * + * **Assumptions**: In addition to the assumption that inputs are in the range [0, 2^88), as in all foreign field gadgets, + * this assumes an additional bound on the inputs: `x * y < 2^264 * p`, where p is the native modulus. + * We usually assert this bound by proving that `x[2] < f[2] + 1`, where `x[2]` is the most significant limb of x. + * To do this, use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. + * The implication is that x and y are _almost_ reduced modulo f. + * + * @example + * ```ts + * // example modulus: secp256k1 prime + * let f = (1n << 256n) - (1n << 32n) - 0b1111010001n; + * + * let x = Provable.witness(Field3.provable, () => Field3.from(f - 1n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); + * + * // range check x, y + * Gadgets.multiRangeCheck(x); + * Gadgets.multiRangeCheck(y); + * + * // prove additional bounds + * let x2Bound = x[2].add((1n << 88n) - 1n - (f >> 176n)); + * let y2Bound = y[2].add((1n << 88n) - 1n - (f >> 176n)); + * Gadgets.multiRangeCheck([x2Bound, y2Bound, Field(0n)]); + * + * // compute x * y mod f + * let z = ForeignField.mul(x, y, f); + * + * Provable.log(z); // ['2', '0', '0'] = limb representation of 2 = (-1)*(-2) mod f + * ``` + */ + mul(x: Field3, y: Field3, f: bigint) { + return ForeignField.mul(x, y, f); + }, + + /** + * TODO + */ + inv(x: Field3, f: bigint) { + return ForeignField.inv(x, f); + }, + + /** + * TODO + */ + div(x: Field3, y: Field3, f: bigint) { + return ForeignField.div(x, y, f); + }, }, /** From 8358f5713db78bdfa4a0f0f6a9d5dfdb4fda04d6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 11:51:10 +0100 Subject: [PATCH 0660/1215] fix unit tests and code tweak --- src/lib/gadgets/foreign-field.ts | 19 ++++++------- src/lib/gadgets/foreign-field.unit-test.ts | 33 +++++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 5fbade75e8..30e02e5e0b 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -35,15 +35,9 @@ const ForeignField = { }, sum, - mul(x: Field3, y: Field3, f: bigint) { - return multiply(x, y, f); - }, - inv(x: Field3, f: bigint) { - return inverse(x, f); - }, - div(x: Field3, y: Field3, f: bigint, { allowZeroOverZero = false } = {}) { - return divide(x, y, f, allowZeroOverZero); - }, + mul: multiply, + inv: inverse, + div: divide, }; /** @@ -165,7 +159,12 @@ function inverse(x: Field3, f: bigint): Field3 { return xInv; } -function divide(x: Field3, y: Field3, f: bigint, allowZeroOverZero = false) { +function divide( + x: Field3, + y: Field3, + f: bigint, + { allowZeroOverZero = false } = {} +) { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 298e0b4ff7..459ee7cdee 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -23,7 +23,6 @@ import { withoutGenerics, } from '../testing/constraint-system.js'; import { GateType } from '../../snarky.js'; -import { ForeignField as ForeignField_ } from './foreign-field.js'; const { ForeignField, Field3 } = Gadgets; @@ -75,14 +74,14 @@ for (let F of fields) { eq2(F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add'); eq2(F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), 'sub'); - eq2(F.mul, (x, y) => ForeignField_.mul(x, y, F.modulus), 'mul'); + eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus), 'mul'); equivalentProvable({ from: [f], to: f })( (x) => F.inverse(x) ?? throwError('no inverse'), - (x) => ForeignField_.inv(x, F.modulus) + (x) => ForeignField.inv(x, F.modulus) ); eq2( (x, y) => F.div(x, y) ?? throwError('no inverse'), - (x, y) => ForeignField_.div(x, y, F.modulus), + (x, y) => ForeignField.div(x, y, F.modulus), 'div' ); @@ -103,19 +102,19 @@ for (let F of fields) { ); equivalentProvable({ from: [big258, big258], to: f })( F.mul, - (x, y) => ForeignField_.mul(x, y, F.modulus), + (x, y) => ForeignField.mul(x, y, F.modulus), 'mul' ); equivalentProvable({ from: [big258], to: f })( (x) => F.inverse(x) ?? throwError('no inverse'), - (x) => ForeignField_.inv(x, F.modulus) + (x) => ForeignField.inv(x, F.modulus) ); // the div() gadget doesn't work with unreduced x because the backwards check (x/y)*y === x fails // and it's not valid with unreduced y because we only assert y != 0, y != f but it can be 2f, 3f, etc. // the combination of inv() and mul() is more flexible (but much more expensive, ~40 vs ~30 constraints) equivalentProvable({ from: [big258, big258], to: f })( (x, y) => F.div(x, y) ?? throwError('no inverse'), - (x, y) => ForeignField_.mul(x, ForeignField_.inv(y, F.modulus), F.modulus) + (x, y) => ForeignField.mul(x, ForeignField.inv(y, F.modulus), F.modulus) ); // sumchain of 5 @@ -163,21 +162,21 @@ let ffProgram = ZkProgram({ mul: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { - return ForeignField_.mul(x, y, F.modulus); + return ForeignField.mul(x, y, F.modulus); }, }, inv: { privateInputs: [Field3.provable], method(x) { - return ForeignField_.inv(x, F.modulus); + return ForeignField.inv(x, F.modulus); }, }, div: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { - return ForeignField_.div(x, y, F.modulus); + return ForeignField.div(x, y, F.modulus); }, }, }, @@ -202,14 +201,20 @@ constraintSystem.fromZkProgram( let mulChain: GateType[] = ['ForeignFieldMul', 'Zero']; let mulLayout = ifNotAllConstant( and( - contains([mulChain, mrc, mrc, mrc]), - withoutGenerics(equals([...mulChain, ...repeat(3, mrc)])) + contains([mulChain, mrc, mrc, mrc, mrc]), + withoutGenerics(equals([...mulChain, ...repeat(4, mrc)])) + ) +); +let invLayout = ifNotAllConstant( + and( + contains([mrc, mulChain, mrc, mrc, mrc]), + withoutGenerics(equals([...mrc, ...mulChain, ...repeat(3, mrc)])) ) ); constraintSystem.fromZkProgram(ffProgram, 'mul', mulLayout); -constraintSystem.fromZkProgram(ffProgram, 'inv', mulLayout); -constraintSystem.fromZkProgram(ffProgram, 'div', mulLayout); +constraintSystem.fromZkProgram(ffProgram, 'inv', invLayout); +constraintSystem.fromZkProgram(ffProgram, 'div', invLayout); // tests with proving From 21b9e8b82834d61290aa112e8cc0f23f223e81b0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 11:54:17 +0100 Subject: [PATCH 0661/1215] document inv and div --- src/lib/gadgets/gadgets.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 79b1e39e06..50ac237eac 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -370,11 +370,6 @@ const Gadgets = { * Foreign field subtraction: `x - y mod f` * * See {@link ForeignField.add} for assumptions and usage examples. - * - * @param x left summand - * @param y right summand - * @param f modulus - * @returns x - y mod f */ sub(x: Field3, y: Field3, f: bigint) { return ForeignField.sub(x, y, f); @@ -452,14 +447,18 @@ const Gadgets = { }, /** - * TODO + * Foreign field inverse: `x^(-1) mod f` + * + * See {@link ForeignField.mul} for assumptions on inputs and usage examples. */ inv(x: Field3, f: bigint) { return ForeignField.inv(x, f); }, /** - * TODO + * Foreign field division: `x * y^(-1) mod f` + * + * See {@link ForeignField.mul} for assumptions on inputs and usage examples. */ div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); From e46432d2c7b317d5b69e6fa49def6d21a701dfb5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 12:01:01 +0100 Subject: [PATCH 0662/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index cea062267c..c8f8c631f2 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit cea062267c2cf81edf50fee8ca9578824c056731 +Subproject commit c8f8c631f28b84c3d3859378a2fe857091207755 From df2ea58bdbcea31171e348f28cb7242d4e33a5c2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 12:13:05 +0100 Subject: [PATCH 0663/1215] minor --- src/lib/gadgets/foreign-field.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 30e02e5e0b..ddac02dc42 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -186,7 +186,7 @@ function divide( let q2Bound = assertMul(z, y, x, f); // range check on q and result bounds - multiRangeCheck([q2Bound, z2Bound, new Field(0n)]); + multiRangeCheck([q2Bound, z2Bound, Field.from(0n)]); if (!allowZeroOverZero) { // assert that y != 0 mod f by checking that it doesn't equal 0 or f @@ -319,9 +319,7 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { } function weakBound(x: Field, f: bigint) { - let f2 = f >> L2; - let f2Bound = lMask - f2; - return x.add(f2Bound); + return x.add(lMask - (f >> L2)); } const Field3 = { From 9d6bb6d1069a9a0ce353d8e96b8363569e810fee Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 12:33:28 +0100 Subject: [PATCH 0664/1215] minor tweaks + changelog --- CHANGELOG.md | 5 +++++ src/lib/gadgets/foreign-field.ts | 5 ++++- src/lib/gadgets/foreign-field.unit-test.ts | 23 +++++++++++----------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0888d6204b..9440ac0ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,10 +19,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) +### Breaking changes + +- Change return signature of `ZkProgram.analyzeMethods()` to be a keyed object https://github.com/o1-labs/o1js/pull/1223 + ### Added - Provable non-native field arithmetic: - `Gadgets.ForeignField.{add, sub, sumchain}()` for addition and subtraction https://github.com/o1-labs/o1js/pull/1220 + - `Gadgets.ForeignField.{mul, inv, div}()` for multiplication and division https://github.com/o1-labs/o1js/pull/1223 - Comprehensive internal testing of constraint system layouts generated by new gadgets https://github.com/o1-labs/o1js/pull/1241 https://github.com/o1-labs/o1js/pull/1220 ### Changed diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index ddac02dc42..c8415be2dc 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -17,7 +17,7 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { ForeignField, Field3, bigint3, Sign }; +export { ForeignField, Field3, Sign }; /** * A 3-tuple of Fields, representing a 3-limb bigint. @@ -202,6 +202,9 @@ function divide( return z; } +/** + * Common logic for gadgets that expect a certain multiplication result instead of just using the remainder. + */ function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, y, f); diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 459ee7cdee..eca513d844 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -77,7 +77,8 @@ for (let F of fields) { eq2(F.mul, (x, y) => ForeignField.mul(x, y, F.modulus), 'mul'); equivalentProvable({ from: [f], to: f })( (x) => F.inverse(x) ?? throwError('no inverse'), - (x) => ForeignField.inv(x, F.modulus) + (x) => ForeignField.inv(x, F.modulus), + 'inv' ); eq2( (x, y) => F.div(x, y) ?? throwError('no inverse'), @@ -92,35 +93,38 @@ for (let F of fields) { equivalentProvable({ from: [big264, big264], to: big264 })( F.add, (x, y) => ForeignField.add(x, y, F.modulus), - 'add' + 'add unreduced' ); // subtraction doesn't work with unreduced y because the range check on the result prevents x-y < -f equivalentProvable({ from: [big264, f], to: big264 })( F.sub, (x, y) => ForeignField.sub(x, y, F.modulus), - 'sub' + 'sub unreduced' ); equivalentProvable({ from: [big258, big258], to: f })( F.mul, (x, y) => ForeignField.mul(x, y, F.modulus), - 'mul' + 'mul unreduced' ); equivalentProvable({ from: [big258], to: f })( (x) => F.inverse(x) ?? throwError('no inverse'), - (x) => ForeignField.inv(x, F.modulus) + (x) => ForeignField.inv(x, F.modulus), + 'inv unreduced' ); // the div() gadget doesn't work with unreduced x because the backwards check (x/y)*y === x fails // and it's not valid with unreduced y because we only assert y != 0, y != f but it can be 2f, 3f, etc. // the combination of inv() and mul() is more flexible (but much more expensive, ~40 vs ~30 constraints) equivalentProvable({ from: [big258, big258], to: f })( (x, y) => F.div(x, y) ?? throwError('no inverse'), - (x, y) => ForeignField.mul(x, ForeignField.inv(y, F.modulus), F.modulus) + (x, y) => ForeignField.mul(x, ForeignField.inv(y, F.modulus), F.modulus), + 'div unreduced' ); // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( (xs, signs) => sum(xs, signs, F), - (xs, signs) => ForeignField.sum(xs, signs, F.modulus) + (xs, signs) => ForeignField.sum(xs, signs, F.modulus), + 'sumchain 5' ); // sumchain up to 100 @@ -137,7 +141,7 @@ for (let F of fields) { let signs = ts.map((t) => t.sign); return ForeignField.sum(xs, signs, F.modulus); }, - 'sumchain' + 'sumchain long' ); } @@ -158,21 +162,18 @@ let ffProgram = ZkProgram({ return ForeignField.sum(xs, signs, F.modulus); }, }, - mul: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { return ForeignField.mul(x, y, F.modulus); }, }, - inv: { privateInputs: [Field3.provable], method(x) { return ForeignField.inv(x, F.modulus); }, }, - div: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { From d27cc073bbc0bd039898d1d7ec7d128b7aa12657 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 13:17:25 +0100 Subject: [PATCH 0665/1215] adapt to ffmul updates --- src/lib/gadgets/elliptic-curve.ts | 41 ++++++++++++++-------------- src/lib/gadgets/foreign-field.ts | 22 +++++++++++---- src/lib/testing/constraint-system.ts | 1 + 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 2fa86d30d8..7952b8603b 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -14,16 +14,17 @@ import { weakBound, } from './foreign-field.js'; import { multiRangeCheck } from './range-check.js'; +import { printGates } from '../testing/constraint-system.js'; type Point = { x: Field3; y: Field3 }; type point = { x: bigint3; y: bigint3; infinity: boolean }; -let { sumChain } = ForeignField; +let { sum } = ForeignField; function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { // witness and range-check slope, x3, y3 let witnesses = exists(9, () => { - let [x1_, x2_, y1_, y2_] = ForeignField.toBigints(x1, x2, y1, y2); + let [x1_, x2_, y1_, y2_] = Field3.toBigints(x1, x2, y1, y2); let denom = inverse(mod(x1_ - x2_, f), f); let m = denom !== undefined ? mod((y1_ - y2_) * denom, f) : 0n; @@ -38,47 +39,45 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - multiRangeCheck(...m); - multiRangeCheck(...x3); - multiRangeCheck(...y3); + multiRangeCheck(m); + multiRangeCheck(x3); + multiRangeCheck(y3); let m2Bound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); // we dont need to bounds check y3[2] because it's never one of the inputs to a multiplication // (x1 - x2)*m = y1 - y2 - let deltaY = sumChain([y1, y2], [-1n], f, { skipRangeCheck: true }); - let deltaX = sumChain([x1, x2], [-1n], f, { + let deltaY = sum([y1, y2], [-1n], f, { skipRangeCheck: true }); + let deltaX = sum([x1, x2], [-1n], f, { skipRangeCheck: true, skipZeroRow: true, }); let qBound1 = assertMul(deltaX, m, deltaY, f); - multiRangeCheck(...deltaX); + multiRangeCheck(deltaX); // m^2 = x1 + x2 + x3 - let xSum = sumChain([x1, x2, x3], [1n, 1n], f, { skipRangeCheck: true }); + let xSum = sum([x1, x2, x3], [1n, 1n], f, { skipRangeCheck: true }); let qBound2 = assertMul(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 - let ySum = sumChain([y1, y3], [1n], f, { skipRangeCheck: true }); - let deltaX1X3 = sumChain([x1, x3], [-1n], f, { + let ySum = sum([y1, y3], [1n], f, { skipRangeCheck: true }); + let deltaX1X3 = sum([x1, x3], [-1n], f, { skipRangeCheck: true, skipZeroRow: true, }); let qBound3 = assertMul(deltaX1X3, m, ySum, f); - multiRangeCheck(...deltaX1X3); + multiRangeCheck(deltaX1X3); // bounds checks - multiRangeCheck(m2Bound, x3Bound, qBound1); - multiRangeCheck(qBound2, qBound3, Field.from(0n)); + multiRangeCheck([m2Bound, x3Bound, qBound1]); + multiRangeCheck([qBound2, qBound3, Field.from(0n)]); } -const Field3_ = provablePure([Field, Field, Field] as TupleN); - let cs = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3_, () => ForeignField.from(0n)); - let x2 = Provable.witness(Field3_, () => ForeignField.from(0n)); - let y1 = Provable.witness(Field3_, () => ForeignField.from(0n)); - let y2 = Provable.witness(Field3_, () => ForeignField.from(0n)); + let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y2 = Provable.witness(Field3.provable, () => Field3.from(0n)); let g = { x: x1, y: y1 }; let h = { x: x2, y: y2 }; @@ -86,4 +85,4 @@ let cs = Provable.constraintSystem(() => { add(g, h, exampleFields.secp256k1.modulus); }); -console.log(cs); +printGates(cs.gates); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 6bec2da7a9..9965aa44ba 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -17,7 +17,16 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { ForeignField, Field3, Sign, split, collapse, weakBound, assertMul }; +export { + ForeignField, + Field3, + bigint3, + Sign, + split, + collapse, + weakBound, + assertMul, +}; /** * A 3-tuple of Fields, representing a 3-limb bigint. @@ -38,10 +47,6 @@ const ForeignField = { mul: multiply, inv: inverse, div: divide, - - toBigints>(...xs: T) { - return Tuple.map(xs, Field3.toBigint); - }, }; /** @@ -349,6 +354,13 @@ const Field3 = { return collapse(bigint3(x)); }, + /** + * Turn several 3-tuples of Fields into bigints + */ + toBigints>(...xs: T) { + return Tuple.map(xs, Field3.toBigint); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 90f3ec1599..55c0cabae0 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -26,6 +26,7 @@ export { withoutGenerics, print, repeat, + printGates, ConstraintSystemTest, }; From b19a3f6a1daabf27c8baaad46817ca332380d255 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 14:50:14 +0100 Subject: [PATCH 0666/1215] replace custom logic in ec add with easy to use assertRank1 method --- src/lib/gadgets/elliptic-curve.ts | 37 ++++------ src/lib/gadgets/foreign-field.ts | 115 +++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 31 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 7952b8603b..b52eda448f 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,14 +1,12 @@ import { inverse, mod } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; -import { provablePure } from '../circuit_value.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; -import { TupleN } from '../util/types.js'; import { exists } from './common.js'; import { Field3, - ForeignField, - assertMul, + Sum, + assertRank1, bigint3, split, weakBound, @@ -19,9 +17,9 @@ import { printGates } from '../testing/constraint-system.js'; type Point = { x: Field3; y: Field3 }; type point = { x: bigint3; y: bigint3; infinity: boolean }; -let { sum } = ForeignField; - function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { + // TODO constant case + // witness and range-check slope, x3, y3 let witnesses = exists(9, () => { let [x1_, x2_, y1_, y2_] = Field3.toBigints(x1, x2, y1, y2); @@ -44,29 +42,21 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { multiRangeCheck(y3); let m2Bound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); - // we dont need to bounds check y3[2] because it's never one of the inputs to a multiplication + // we dont need to bounds-check y3[2] because it's never one of the inputs to a multiplication // (x1 - x2)*m = y1 - y2 - let deltaY = sum([y1, y2], [-1n], f, { skipRangeCheck: true }); - let deltaX = sum([x1, x2], [-1n], f, { - skipRangeCheck: true, - skipZeroRow: true, - }); - let qBound1 = assertMul(deltaX, m, deltaY, f); - multiRangeCheck(deltaX); + let deltaX = new Sum(x1).sub(x2); + let deltaY = new Sum(y1).sub(y2); + let qBound1 = assertRank1(deltaX, m, deltaY, f); // m^2 = x1 + x2 + x3 - let xSum = sum([x1, x2, x3], [1n, 1n], f, { skipRangeCheck: true }); - let qBound2 = assertMul(m, m, xSum, f); + let xSum = new Sum(x1).add(x2).add(x3); + let qBound2 = assertRank1(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 - let ySum = sum([y1, y3], [1n], f, { skipRangeCheck: true }); - let deltaX1X3 = sum([x1, x3], [-1n], f, { - skipRangeCheck: true, - skipZeroRow: true, - }); - let qBound3 = assertMul(deltaX1X3, m, ySum, f); - multiRangeCheck(deltaX1X3); + let deltaX1X3 = new Sum(x1).sub(x3); + let ySum = new Sum(y1).add(y3); + let qBound3 = assertRank1(deltaX1X3, m, ySum, f); // bounds checks multiRangeCheck([m2Bound, x3Bound, qBound1]); @@ -86,3 +76,4 @@ let cs = Provable.constraintSystem(() => { }); printGates(cs.gates); +console.log({ digest: cs.digest, rows: cs.rows }); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 9965aa44ba..f460bea0a2 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -26,6 +26,8 @@ export { collapse, weakBound, assertMul, + Sum, + assertRank1, }; /** @@ -54,12 +56,7 @@ const ForeignField = { * * assumes that inputs are range checked, does range check on the result. */ -function sum( - x: Field3[], - sign: Sign[], - f: bigint, - { skipRangeCheck = false, skipZeroRow = false } = {} -) { +function sum(x: Field3[], sign: Sign[], f: bigint) { assert(x.length === sign.length + 1, 'inputs and operators match'); // constant case @@ -75,10 +72,10 @@ function sum( ({ result } = singleAdd(result, x[i + 1], sign[i], f)); } // final zero row to hold result - if (!skipZeroRow) Gates.zero(...result); + Gates.zero(...result); // range check result - if (!skipRangeCheck) multiRangeCheck(result); + multiRangeCheck(result); return result; } @@ -394,3 +391,105 @@ function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { function split2(x: bigint): [bigint, bigint] { return [x & lMask, (x >> L) & lMask]; } + +/** + * Optimized multiplication of sums, like (x + y)*z = a + b + c + * + * We use two optimizations over naive summing and then multiplying: + * + * - we skip the range check on the remainder sum, because ffmul is sound with r being a sum of range-checked values + * - we chain the first input's sum into the ffmul gate + * + * As usual, all values are assumed to be range checked, and the left and right multiplication inputs + * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. + * However, all extra checks that are needed on the sums are handled here. + * + * TODO example + */ +function assertRank1( + x: Field3 | Sum, + y: Field3 | Sum, + xy: Field3 | Sum, + f: bigint +) { + x = Sum.fromUnfinished(x, f); + y = Sum.fromUnfinished(y, f); + xy = Sum.fromUnfinished(xy, f); + + // finish the y and xy sums with a zero gate + let y0 = y.finish(f); + let xy0 = xy.finish(f); + + // x is chained into the ffmul gate + let x0 = x.finishForChaining(f); + let q2Bound = assertMul(x0, y0, xy0, f); + + // we need an extra range check on x and y, but not xy + x.rangeCheck(); + y.rangeCheck(); + + return q2Bound; +} + +class Sum { + #result?: Field3; + #summands: Field3[]; + #ops: Sign[] = []; + + constructor(x: Field3) { + this.#summands = [x]; + } + + get result() { + assert(this.#result !== undefined, 'sum not finished'); + return this.#result; + } + + add(y: Field3) { + assert(this.#result === undefined, 'sum already finished'); + this.#ops.push(1n); + this.#summands.push(y); + return this; + } + + sub(y: Field3) { + assert(this.#result === undefined, 'sum already finished'); + this.#ops.push(-1n); + this.#summands.push(y); + return this; + } + + finish(f: bigint, forChaining = false) { + assert(this.#result === undefined, 'sum already finished'); + let signs = this.#ops; + let n = signs.length; + + let x = this.#summands.map(toVars); + let result = x[0]; + + for (let i = 0; i < n; i++) { + ({ result } = singleAdd(result, x[i + 1], signs[i], f)); + } + if (n > 0 && !forChaining) Gates.zero(...result); + + this.#result = result; + return result; + } + + finishForChaining(f: bigint) { + return this.finish(f, true); + } + + rangeCheck() { + assert(this.#result !== undefined, 'sum not finished'); + if (this.#ops.length > 0) multiRangeCheck(this.#result); + } + + static fromUnfinished(x: Field3 | Sum, f: bigint) { + if (x instanceof Sum) { + assert(x.#result === undefined, 'sum already finished'); + return x; + } + return new Sum(x); + } +} From 6c34c9f4074da828003e2595c778def85c7a320e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 16:37:33 +0100 Subject: [PATCH 0667/1215] double --- src/lib/gadgets/elliptic-curve.ts | 75 ++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index b52eda448f..4539b8ed33 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -5,6 +5,7 @@ import { Provable } from '../provable.js'; import { exists } from './common.js'; import { Field3, + ForeignField, Sum, assertRank1, bigint3, @@ -40,9 +41,9 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { multiRangeCheck(m); multiRangeCheck(x3); multiRangeCheck(y3); - let m2Bound = weakBound(m[2], f); + let mBound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); - // we dont need to bounds-check y3[2] because it's never one of the inputs to a multiplication + // we dont need to bound y3[2] because it's never one of the inputs to a multiplication // (x1 - x2)*m = y1 - y2 let deltaX = new Sum(x1).sub(x2); @@ -59,11 +60,61 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { let qBound3 = assertRank1(deltaX1X3, m, ySum, f); // bounds checks - multiRangeCheck([m2Bound, x3Bound, qBound1]); + multiRangeCheck([mBound, x3Bound, qBound1]); multiRangeCheck([qBound2, qBound3, Field.from(0n)]); } -let cs = Provable.constraintSystem(() => { +function double({ x: x1, y: y1 }: Point, f: bigint) { + // TODO constant case + + // witness and range-check slope, x3, y3 + let witnesses = exists(9, () => { + let [x1_, y1_] = Field3.toBigints(x1, y1); + let denom = inverse(mod(2n * y1_, f), f); + + let m = denom !== undefined ? mod(3n * mod(x1_ ** 2n, f) * denom, f) : 0n; + let m2 = mod(m * m, f); + let x3 = mod(m2 - 2n * x1_, f); + let y3 = mod(m * (x1_ - x3) - y1_, f); + + return [...split(m), ...split(x3), ...split(y3)]; + }); + let [m0, m1, m2, x30, x31, x32, y30, y31, y32] = witnesses; + let m: Field3 = [m0, m1, m2]; + let x3: Field3 = [x30, x31, x32]; + let y3: Field3 = [y30, y31, y32]; + + multiRangeCheck(m); + multiRangeCheck(x3); + multiRangeCheck(y3); + let mBound = weakBound(m[2], f); + let x3Bound = weakBound(x3[2], f); + // we dont need to bound y3[2] because it's never one of the inputs to a multiplication + + // x1^2 = x1x1 + let x1x1 = ForeignField.mul(x1, x1, f); + + // 2*y1*m = 3*x1x1 + // TODO this assumes the curve has a == 0 + let y1Times2 = new Sum(y1).add(y1); + let x1x1Times3 = new Sum(x1x1).add(x1x1).add(x1x1); + let qBound1 = assertRank1(y1Times2, m, x1x1Times3, f); + + // m^2 = 2*x1 + x3 + let xSum = new Sum(x1).add(x1).add(x3); + let qBound2 = assertRank1(m, m, xSum, f); + + // (x1 - x3)*m = y1 + y3 + let deltaX1X3 = new Sum(x1).sub(x3); + let ySum = new Sum(y1).add(y3); + let qBound3 = assertRank1(deltaX1X3, m, ySum, f); + + // bounds checks + multiRangeCheck([mBound, x3Bound, qBound1]); + multiRangeCheck([qBound2, qBound3, Field.from(0n)]); +} + +let csAdd = Provable.constraintSystem(() => { let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); @@ -75,5 +126,17 @@ let cs = Provable.constraintSystem(() => { add(g, h, exampleFields.secp256k1.modulus); }); -printGates(cs.gates); -console.log({ digest: cs.digest, rows: cs.rows }); +let csDouble = Provable.constraintSystem(() => { + let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + + let g = { x: x1, y: y1 }; + + double(g, exampleFields.secp256k1.modulus); +}); + +printGates(csAdd.gates); +console.log({ digest: csAdd.digest, rows: csAdd.rows }); + +printGates(csDouble.gates); +console.log({ digest: csDouble.digest, rows: csDouble.rows }); From d41f30f2a9dfbd7cfb0a27c5f6a583d986cdfd6b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 17:24:01 +0100 Subject: [PATCH 0668/1215] tweak cs printing --- src/lib/testing/constraint-system.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 55c0cabae0..048cddeec7 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -445,9 +445,7 @@ function wiresToPretty(wires: Gate['wires'], row: number) { if (wire.row === row) { strWires.push(`${col}->${wire.col}`); } else { - let rowDelta = wire.row - row; - let rowStr = rowDelta > 0 ? `+${rowDelta}` : `${rowDelta}`; - strWires.push(`${col}->(${rowStr},${wire.col})`); + strWires.push(`${col}->(${wire.row},${wire.col})`); } } return strWires.join(', '); From 4c3f6a5fa5ac733bf7eff1c35fae08afae131c58 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 17:24:11 +0100 Subject: [PATCH 0669/1215] initial aggregator for scaling --- src/lib/gadgets/elliptic-curve.ts | 43 +++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 4539b8ed33..5f1c9989ee 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,8 +1,12 @@ -import { inverse, mod } from '../../bindings/crypto/finite_field.js'; +import { + FiniteField, + inverse, + mod, +} from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; -import { exists } from './common.js'; +import { assert, exists } from './common.js'; import { Field3, ForeignField, @@ -14,6 +18,9 @@ import { } from './foreign-field.js'; import { multiRangeCheck } from './range-check.js'; import { printGates } from '../testing/constraint-system.js'; +import { sha256 } from 'js-sha256'; +import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; +import { Pallas } from '../../bindings/crypto/elliptic_curve.js'; type Point = { x: Field3; y: Field3 }; type point = { x: bigint3; y: bigint3; infinity: boolean }; @@ -114,6 +121,34 @@ function double({ x: x1, y: y1 }: Point, f: bigint) { multiRangeCheck([qBound2, qBound3, Field.from(0n)]); } +/** + * For EC scalar multiplication we use an initial point which is subtracted + * at the end, to avoid encountering the point at infinity. + * + * This is a simple hash-to-group algorithm which finds that initial point. + * It's important that this point has no known discrete logarithm so that nobody + * can create an invalid proof of EC scaling. + */ +function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { + let h = sha256.create(); + h.update('o1js:ecdsa'); + let bytes = h.array(); + + // bytes represent a 256-bit number + // use that as x coordinate + let x = F.mod(bytesToBigInt(bytes)); + let y: bigint | undefined = undefined; + + // increment x until we find a y coordinate + while (y === undefined) { + // solve y^2 = x^3 + ax + b + let x3 = F.mul(F.square(x), x); + let y2 = F.add(x3, F.mul(a, x) + b); + y = F.sqrt(y2); + } + return { x: F.mod(x), y, infinity: false }; +} + let csAdd = Provable.constraintSystem(() => { let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); @@ -140,3 +175,7 @@ console.log({ digest: csAdd.digest, rows: csAdd.rows }); printGates(csDouble.gates); console.log({ digest: csDouble.digest, rows: csDouble.rows }); + +let point = initialAggregator(exampleFields.Fp, { a: 0n, b: 5n }); +console.log({ point }); +assert(Pallas.isOnCurve(Pallas.fromAffine(point))); From c99cee26509c7a992b72e1f76db5c08b3be66100 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:10:37 +0100 Subject: [PATCH 0670/1215] another helper method on Field3 --- src/lib/gadgets/foreign-field.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index f460bea0a2..bc59eeff09 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -60,7 +60,7 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { assert(x.length === sign.length + 1, 'inputs and operators match'); // constant case - if (x.every((x) => x.every((x) => x.isConstant()))) { + if (x.every(Field3.isConstant)) { let xBig = x.map(Field3.toBigint); let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); return Field3.from(mod(sum, f)); @@ -122,7 +122,7 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (a.every((x) => x.isConstant()) && b.every((x) => x.isConstant())) { + if (Field3.isConstant(a) && Field3.isConstant(b)) { let ab = Field3.toBigint(a) * Field3.toBigint(b); return Field3.from(mod(ab, f)); } @@ -146,7 +146,7 @@ function inverse(x: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (x.every((x) => x.isConstant())) { + if (Field3.isConstant(x)) { let xInv = modInverse(Field3.toBigint(x), f); assert(xInv !== undefined, 'inverse exists'); return Field3.from(xInv); @@ -179,7 +179,7 @@ function divide( assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (x.every((x) => x.isConstant()) && y.every((x) => x.isConstant())) { + if (Field3.isConstant(x) && Field3.isConstant(y)) { let yInv = modInverse(Field3.toBigint(y), f); assert(yInv !== undefined, 'inverse exists'); return Field3.from(mod(Field3.toBigint(x) * yInv, f)); @@ -358,6 +358,13 @@ const Field3 = { return Tuple.map(xs, Field3.toBigint); }, + /** + * Check whether a 3-tuple of Fields is constant + */ + isConstant(x: Field3) { + return x.every((x) => x.isConstant()); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * From 95017b30f9202963e89645e440437e080a501d77 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:11:17 +0100 Subject: [PATCH 0671/1215] add constant ecdsa implementation and helper types --- src/lib/gadgets/elliptic-curve.ts | 79 ++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 5f1c9989ee..b14384480c 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -20,10 +20,14 @@ import { multiRangeCheck } from './range-check.js'; import { printGates } from '../testing/constraint-system.js'; import { sha256 } from 'js-sha256'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; -import { Pallas } from '../../bindings/crypto/elliptic_curve.js'; +import { CurveAffine, Pallas } from '../../bindings/crypto/elliptic_curve.js'; +import { Bool } from '../bool.js'; type Point = { x: Field3; y: Field3 }; -type point = { x: bigint3; y: bigint3; infinity: boolean }; +type point = { x: bigint; y: bigint; infinity: boolean }; + +type Signature = { r: Field3; s: Field3 }; +type signature = { r: bigint; s: bigint }; function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { // TODO constant case @@ -121,6 +125,59 @@ function double({ x: x1, y: y1 }: Point, f: bigint) { multiRangeCheck([qBound2, qBound3, Field.from(0n)]); } +function verifyEcdsa( + Curve: CurveAffine, + signature: Signature, + msgHash: Field3, + publicKey: Point +) { + // constant case + if ( + Signature.isConstant(signature) && + Field3.isConstant(msgHash) && + Point.isConstant(publicKey) + ) { + let isValid = verifyEcdsaConstant( + Curve, + Signature.toBigint(signature), + Field3.toBigint(msgHash), + Point.toBigint(publicKey) + ); + assert(isValid, 'invalid signature'); + return; + } + + // provable case + // TODO +} + +/** + * Bigint implementation of ECDSA verify + */ +function verifyEcdsaConstant( + Curve: CurveAffine, + { r, s }: { r: bigint; s: bigint }, + msgHash: bigint, + publicKey: { x: bigint; y: bigint } +) { + let q = Curve.order; + let QA = Curve.fromNonzero(publicKey); + if (!Curve.isOnCurve(QA)) return false; + if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; + if (r < 1n || r >= Curve.order) return false; + if (s < 1n || s >= Curve.order) return false; + + let sInv = inverse(s, q); + if (sInv === undefined) throw Error('impossible'); + let u1 = mod(msgHash * sInv, q); + let u2 = mod(r * sInv, q); + + let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); + if (Curve.equal(X, Curve.zero)) return false; + + return mod(X.x, q) === r; +} + /** * For EC scalar multiplication we use an initial point which is subtracted * at the end, to avoid encountering the point at infinity. @@ -149,6 +206,24 @@ function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { return { x: F.mod(x), y, infinity: false }; } +const Point = { + toBigint({ x, y }: Point): point { + return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; + }, + isConstant({ x, y }: Point) { + return Field3.isConstant(x) && Field3.isConstant(y); + }, +}; + +const Signature = { + toBigint({ r, s }: Signature): signature { + return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; + }, + isConstant({ s, r }: Signature) { + return Field3.isConstant(s) && Field3.isConstant(r); + }, +}; + let csAdd = Provable.constraintSystem(() => { let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); From dd2ebd391414edb71175bde1801d0a91e6809c38 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:11:25 +0100 Subject: [PATCH 0672/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index c8f8c631f2..9b2f1abfeb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c8f8c631f28b84c3d3859378a2fe857091207755 +Subproject commit 9b2f1abfeb891e95ccb88e536275eb6733e67f30 From fc6f1f83db74646c47ce2780a89786663138a02e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:47:49 +0100 Subject: [PATCH 0673/1215] helper --- src/lib/gadgets/foreign-field.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index bc59eeff09..bb948c1757 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -365,6 +365,15 @@ const Field3 = { return x.every((x) => x.isConstant()); }, + /** + * Assert that two 3-tuples of Fields are equal + */ + assertEqual(x: Field3, y: Field3) { + x[0].assertEquals(y[0]); + x[1].assertEquals(y[1]); + x[2].assertEquals(y[2]); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * From 00a11f7403aeefd75add0e83ea40a1fed9366efc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:53:52 +0100 Subject: [PATCH 0674/1215] stub out provable ecdsa, skipping the hard part --- src/lib/gadgets/elliptic-curve.ts | 61 ++++++++++++++++++++++++++++++- src/lib/gadgets/foreign-field.ts | 8 ++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index b14384480c..03d93d3972 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -73,6 +73,8 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { // bounds checks multiRangeCheck([mBound, x3Bound, qBound1]); multiRangeCheck([qBound2, qBound3, Field.from(0n)]); + + return { x: x3, y: y3 }; } function double({ x: x1, y: y1 }: Point, f: bigint) { @@ -123,13 +125,20 @@ function double({ x: x1, y: y1 }: Point, f: bigint) { // bounds checks multiRangeCheck([mBound, x3Bound, qBound1]); multiRangeCheck([qBound2, qBound3, Field.from(0n)]); + + return { x: x3, y: y3 }; } function verifyEcdsa( Curve: CurveAffine, + IA: point, signature: Signature, msgHash: Field3, - publicKey: Point + publicKey: Point, + table?: { + windowSize: number; // what we called c before + multiples?: point[]; // 0, G, 2*G, ..., (2^c-1)*G + } ) { // constant case if ( @@ -148,7 +157,45 @@ function verifyEcdsa( } // provable case - // TODO + // TODO should check that the publicKey is a valid point? probably not + + let { r, s } = signature; + let sInv = ForeignField.inv(s, Curve.order); + let u1 = ForeignField.mul(msgHash, sInv, Curve.order); + let u2 = ForeignField.mul(r, sInv, Curve.order); + + let X = varPlusFixedScalarMul(Curve, IA, u1, publicKey, u2, table); + + // assert that X != IA, and add -IA + Point.equal(X, Point.from(IA)).assertFalse(); + X = add(X, Point.from(Curve.negate(IA)), Curve.order); + + // TODO reduce X.x mod the scalar order + Field3.assertEqual(X.x, r); +} + +/** + * Scalar mul that we need for ECDSA: + * + * IA + s*P + t*G, + * + * where IA is the initial aggregator, P is any point and G is the generator. + * + * We double both points together and leverage a precomputed table + * of size 2^c to avoid all but every cth addition for t*G. + */ +function varPlusFixedScalarMul( + Curve: CurveAffine, + IA: point, + s: Field3, + P: Point, + t: Field3, + table?: { + windowSize: number; // what we called c before + multiples?: point[]; // 0, G, 2*G, ..., (2^c-1)*G + } +): Point { + throw Error('TODO'); } /** @@ -207,12 +254,22 @@ function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { } const Point = { + from({ x, y }: point): Point { + return { x: Field3.from(x), y: Field3.from(y) }; + }, toBigint({ x, y }: Point): point { return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; }, isConstant({ x, y }: Point) { return Field3.isConstant(x) && Field3.isConstant(y); }, + assertEqual({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point) { + Field3.assertEqual(x1, x2); + Field3.assertEqual(y1, y2); + }, + equal({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point) { + return Field3.equal(x1, x2).and(Field3.equal(y1, y2)); + }, }; const Signature = { diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index bb948c1757..265859dcb9 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -374,6 +374,14 @@ const Field3 = { x[2].assertEquals(y[2]); }, + /** + * Check whether two 3-tuples of Fields are equal + */ + equal(x: Field3, y: Field3) { + let eq01 = x[0].add(x[1].mul(1n << L)).equals(y[0].add(y[1].mul(1n << L))); + return eq01.and(x[2].equals(y[2])); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * From 1cde96368a89c3cb6e26704a88cc63828c6f2e71 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 15 Nov 2023 11:20:47 -0800 Subject: [PATCH 0675/1215] docs(README-dev.md): update build instructions and add details about build scripts - Replace opam with Dune in the list of required tools to reflect changes in the build process - Add a new section about build scripts, explaining the role of update-snarkyjs-bindings.sh - Expand on the OCaml bindings section, detailing the use of Dune and Js_of_ocaml in the build process - Add information about the WebAssembly bindings build process, including the output files - Introduce a section about generated constant types, explaining how they are created and their role in ensuring protocol consistency --- README-dev.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README-dev.md b/README-dev.md index 248aafa463..35f0bd2d88 100644 --- a/README-dev.md +++ b/README-dev.md @@ -13,7 +13,7 @@ Before starting, ensure you have the following tools installed: - [Git](https://git-scm.com/) - [Node.js and npm](https://nodejs.org/) -- [opam](https://opam.ocaml.org/) +- [Dune](https://github.com/ocaml/dune) - [Cargo](https://www.rust-lang.org/learn/get-started) After cloning the repository, you need to fetch the submodules: @@ -49,14 +49,35 @@ npm run build:bindings This will build the OCaml and Rust artifacts, and copy them to the `src/bindings/compiled` directory. +### Build Scripts + +The root build script which kicks off the build process is under `src/bindings/scripts/update-snarkyjs-bindings.sh`. This script is responsible for building the Node.js and web artifacts for o1js, and places them under `src/bindings/compiled`, to be used by o1js. + ### OCaml Bindings o1js depends on Pickles, snarky, and parts of the Mina transaction logic, all of which are compiled to JavaScript and stored as artifacts to be used by o1js natively. The OCaml bindings are located under `src/bindings`. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. +To compile the OCaml code, a build tool called Dune is used. Dune is a build system for OCaml projects, and is used in addition with Js_of_ocaml to compile the OCaml code to JavaScript. The dune file that is responsible for compiling the OCaml code is located under `src/bindings/ocaml/dune`. There are two build targets: `snarky_js_node` and `snarky_js_web`, which compile the Mina dependencies as well as link the wasm artifacts to build the Node.js and web artifacts, respectively. The output file is `snark_js_node.bc.js`, which is used by o1js. + ### WebAssembly Bindings o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo, under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. +To compile the wasm code, a combination of Cargo and Dune is used. Both build files are located under `src/mina/src/lib/crypto/kimchi`, where the `wasm` folder contains the Rust code which is compiled to wasm, and the `js` folder which contains a wrapper around the wasm code which allows Js_of_ocaml to compile against the wasm backend. + +For the wasm build, the output files are: + +- `plonk_wasm_bg.wasm.d.ts`: TypeScript definition files describing the types of .wasm or .js files. +- `plonk_wasm.d.ts`: TypeScript definition file for plonk_wasm.js. +- `plonk_wasm_bg.wasm`: The compiled WebAssembly binary. +- `plonk_wasm.js`: JavaScript file that wraps the WASM code for use in Node.js. + +### Generated Constant Types + +In addition to building the OCaml and Rust code, the build script also generates TypeScript types for constants used in the Mina protocol. These types are generated from the OCaml source files, and are located under `src/bindings/crypto/constants.ts` and `src/bindings/mina-transaction/gen`. When building the bindings, these constants are auto-generated by Dune. If you wish to add a new constant, you can edit the `src/bindings/ocaml/snarky_js_constants` file, and then run `npm run build:bindings` to regenerate the TypeScript files. + +These types are used by o1js to ensure that the constants used in the protocol are consistent with the OCaml source files. + ## Development ### Branch Compatibility From ce389efcbfa58a7a9dfae1bcb6eb6577463388c0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 21:02:26 +0100 Subject: [PATCH 0676/1215] revert explosion of helper methods in favor of general interface --- src/lib/gadgets/elliptic-curve.ts | 31 +++++++++++-------------------- src/lib/gadgets/foreign-field.ts | 17 ----------------- src/lib/provable.ts | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 03d93d3972..707fd2f423 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -22,6 +22,7 @@ import { sha256 } from 'js-sha256'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { CurveAffine, Pallas } from '../../bindings/crypto/elliptic_curve.js'; import { Bool } from '../bool.js'; +import { provable } from '../circuit_value.js'; type Point = { x: Field3; y: Field3 }; type point = { x: bigint; y: bigint; infinity: boolean }; @@ -131,7 +132,7 @@ function double({ x: x1, y: y1 }: Point, f: bigint) { function verifyEcdsa( Curve: CurveAffine, - IA: point, + ia: point, signature: Signature, msgHash: Field3, publicKey: Point, @@ -142,9 +143,9 @@ function verifyEcdsa( ) { // constant case if ( - Signature.isConstant(signature) && + Provable.isConstant(Signature, signature) && Field3.isConstant(msgHash) && - Point.isConstant(publicKey) + Provable.isConstant(Point, publicKey) ) { let isValid = verifyEcdsaConstant( Curve, @@ -164,14 +165,15 @@ function verifyEcdsa( let u1 = ForeignField.mul(msgHash, sInv, Curve.order); let u2 = ForeignField.mul(r, sInv, Curve.order); + let IA = Point.from(ia); let X = varPlusFixedScalarMul(Curve, IA, u1, publicKey, u2, table); // assert that X != IA, and add -IA - Point.equal(X, Point.from(IA)).assertFalse(); - X = add(X, Point.from(Curve.negate(IA)), Curve.order); + Provable.equal(Point, X, IA).assertFalse(); + X = add(X, Point.from(Curve.negate(ia)), Curve.order); // TODO reduce X.x mod the scalar order - Field3.assertEqual(X.x, r); + Provable.assertEqual(Field3.provable, X.x, r); } /** @@ -186,7 +188,7 @@ function verifyEcdsa( */ function varPlusFixedScalarMul( Curve: CurveAffine, - IA: point, + IA: Point, s: Field3, P: Point, t: Field3, @@ -254,31 +256,20 @@ function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { } const Point = { + ...provable({ x: Field3.provable, y: Field3.provable }), from({ x, y }: point): Point { return { x: Field3.from(x), y: Field3.from(y) }; }, toBigint({ x, y }: Point): point { return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; }, - isConstant({ x, y }: Point) { - return Field3.isConstant(x) && Field3.isConstant(y); - }, - assertEqual({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point) { - Field3.assertEqual(x1, x2); - Field3.assertEqual(y1, y2); - }, - equal({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point) { - return Field3.equal(x1, x2).and(Field3.equal(y1, y2)); - }, }; const Signature = { + ...provable({ r: Field3.provable, s: Field3.provable }), toBigint({ r, s }: Signature): signature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, - isConstant({ s, r }: Signature) { - return Field3.isConstant(s) && Field3.isConstant(r); - }, }; let csAdd = Provable.constraintSystem(() => { diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 265859dcb9..bc59eeff09 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -365,23 +365,6 @@ const Field3 = { return x.every((x) => x.isConstant()); }, - /** - * Assert that two 3-tuples of Fields are equal - */ - assertEqual(x: Field3, y: Field3) { - x[0].assertEquals(y[0]); - x[1].assertEquals(y[1]); - x[2].assertEquals(y[2]); - }, - - /** - * Check whether two 3-tuples of Fields are equal - */ - equal(x: Field3, y: Field3) { - let eq01 = x[0].add(x[1].mul(1n << L)).equals(y[0].add(y[1].mul(1n << L))); - return eq01.and(x[2].equals(y[2])); - }, - /** * Provable interface for `Field3 = [Field, Field, Field]`. * diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 0b1a5e00aa..bd90d66455 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -3,6 +3,7 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ +import { FieldVar } from './field.js'; import { Field, Bool } from './core.js'; import { Provable as Provable_, Snarky } from '../snarky.js'; import type { FlexibleProvable, ProvableExtended } from './circuit_value.js'; @@ -119,6 +120,16 @@ const Provable = { * ``` */ Array: provableArray, + /** + * Check whether a value is constant. + * See {@link FieldVar} for more information about constants and variables. + * + * @example + * ```ts + * let x = Field(42); + * Provable.isConstant(x); // true + */ + isConstant, /** * Interface to log elements within a circuit. Similar to `console.log()`. * @example @@ -392,6 +403,10 @@ function switch_>( return (type as Provable).fromFields(fields, aux); } +function isConstant(type: Provable, x: T): boolean { + return type.toFields(x).every((x) => x.isConstant()); +} + // logging in provable code function log(...args: any) { From e4589070d6c4bcca951d69124b5f0f0f0aa29ff1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 21:14:37 +0100 Subject: [PATCH 0677/1215] add constant cases and a bit more ecdsa logic --- src/lib/gadgets/elliptic-curve.ts | 52 ++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 707fd2f423..4f01e3c243 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -12,7 +12,6 @@ import { ForeignField, Sum, assertRank1, - bigint3, split, weakBound, } from './foreign-field.js'; @@ -20,18 +19,36 @@ import { multiRangeCheck } from './range-check.js'; import { printGates } from '../testing/constraint-system.js'; import { sha256 } from 'js-sha256'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; -import { CurveAffine, Pallas } from '../../bindings/crypto/elliptic_curve.js'; +import { + CurveAffine, + Pallas, + affineAdd, + affineDouble, +} from '../../bindings/crypto/elliptic_curve.js'; import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; +/** + * Non-zero elliptic curve point in affine coordinates. + */ type Point = { x: Field3; y: Field3 }; -type point = { x: bigint; y: bigint; infinity: boolean }; +type point = { x: bigint; y: bigint }; +/** + * ECDSA signature consisting of two curve scalars. + */ type Signature = { r: Field3; s: Field3 }; type signature = { r: bigint; s: bigint }; -function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { - // TODO constant case +function add(p1: Point, p2: Point, f: bigint) { + let { x: x1, y: y1 } = p1; + let { x: x2, y: y2 } = p2; + + // constant case + if (Provable.isConstant(Point, p1) && Provable.isConstant(Point, p2)) { + let p3 = affineAdd(Point.toBigint(p1), Point.toBigint(p2), f); + return Point.from(p3); + } // witness and range-check slope, x3, y3 let witnesses = exists(9, () => { @@ -78,8 +95,14 @@ function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { return { x: x3, y: y3 }; } -function double({ x: x1, y: y1 }: Point, f: bigint) { - // TODO constant case +function double(p1: Point, f: bigint) { + let { x: x1, y: y1 } = p1; + + // constant case + if (Provable.isConstant(Point, p1)) { + let p3 = affineDouble(Point.toBigint(p1), f); + return Point.from(p3); + } // witness and range-check slope, x3, y3 let witnesses = exists(9, () => { @@ -158,7 +181,7 @@ function verifyEcdsa( } // provable case - // TODO should check that the publicKey is a valid point? probably not + // TODO should we check that the publicKey is a valid point? probably not let { r, s } = signature; let sInv = ForeignField.inv(s, Curve.order); @@ -166,14 +189,15 @@ function verifyEcdsa( let u2 = ForeignField.mul(r, sInv, Curve.order); let IA = Point.from(ia); - let X = varPlusFixedScalarMul(Curve, IA, u1, publicKey, u2, table); + let R = varPlusFixedScalarMul(Curve, IA, u1, publicKey, u2, table); // assert that X != IA, and add -IA - Provable.equal(Point, X, IA).assertFalse(); - X = add(X, Point.from(Curve.negate(ia)), Curve.order); + Provable.equal(Point, R, IA).assertFalse(); + R = add(R, Point.from(Curve.negate(Curve.fromNonzero(ia))), Curve.order); - // TODO reduce X.x mod the scalar order - Provable.assertEqual(Field3.provable, X.x, r); + // reduce R.x modulo the curve order + let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); + Provable.assertEqual(Field3.provable, Rx, r); } /** @@ -260,7 +284,7 @@ const Point = { from({ x, y }: point): Point { return { x: Field3.from(x), y: Field3.from(y) }; }, - toBigint({ x, y }: Point): point { + toBigint({ x, y }: Point) { return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; }, }; From 8db8d06546e7184548bb0e8a5bd95936771a7837 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 21:14:42 +0100 Subject: [PATCH 0678/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 9b2f1abfeb..0c9a907ca5 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9b2f1abfeb891e95ccb88e536275eb6733e67f30 +Subproject commit 0c9a907ca5dad975b6677e3f7d5d3f86bb9e6fdc From ab9584fe7e11fb17e4c6284110c97ac991497bef Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 13:18:57 +0100 Subject: [PATCH 0679/1215] implement ecdsa --- src/lib/gadgets/elliptic-curve.ts | 221 ++++++++++++++++++++++++++---- 1 file changed, 198 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 4f01e3c243..3f24a8a346 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -15,10 +15,13 @@ import { split, weakBound, } from './foreign-field.js'; -import { multiRangeCheck } from './range-check.js'; +import { L, multiRangeCheck } from './range-check.js'; import { printGates } from '../testing/constraint-system.js'; import { sha256 } from 'js-sha256'; -import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; +import { + bigIntToBits, + bytesToBigInt, +} from '../../bindings/crypto/bigint-helpers.js'; import { CurveAffine, Pallas, @@ -27,6 +30,7 @@ import { } from '../../bindings/crypto/elliptic_curve.js'; import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; +import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; /** * Non-zero elliptic curve point in affine coordinates. @@ -159,9 +163,11 @@ function verifyEcdsa( signature: Signature, msgHash: Field3, publicKey: Point, - table?: { - windowSize: number; // what we called c before - multiples?: point[]; // 0, G, 2*G, ..., (2^c-1)*G + tables?: { + windowSizeG?: number; + multiplesG?: Point[]; + windowSizeP?: number; + multiplesP?: Point[]; } ) { // constant case @@ -182,18 +188,14 @@ function verifyEcdsa( // provable case // TODO should we check that the publicKey is a valid point? probably not - let { r, s } = signature; let sInv = ForeignField.inv(s, Curve.order); let u1 = ForeignField.mul(msgHash, sInv, Curve.order); let u2 = ForeignField.mul(r, sInv, Curve.order); - let IA = Point.from(ia); - let R = varPlusFixedScalarMul(Curve, IA, u1, publicKey, u2, table); - - // assert that X != IA, and add -IA - Provable.equal(Point, R, IA).assertFalse(); - R = add(R, Point.from(Curve.negate(Curve.fromNonzero(ia))), Curve.order); + let G = Point.from(Curve.one); + let R = doubleScalarMul(Curve, ia, u1, G, u2, publicKey, tables); + // this ^ already proves that R != 0 // reduce R.x modulo the curve order let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); @@ -203,25 +205,75 @@ function verifyEcdsa( /** * Scalar mul that we need for ECDSA: * - * IA + s*P + t*G, + * s*G + t*P, * - * where IA is the initial aggregator, P is any point and G is the generator. + * where G, P are any points. The result is not allowed to be zero. * * We double both points together and leverage a precomputed table * of size 2^c to avoid all but every cth addition for t*G. + * + * TODO: could use lookups for picking precomputed multiples, instead of O(2^c) provable switch + * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case + * TODO: glv trick which cuts down ec doubles by half by splitting s*P = s0*P + s1*endo(P) with s0, s1 in [0, 2^128) */ -function varPlusFixedScalarMul( +function doubleScalarMul( Curve: CurveAffine, - IA: Point, + ia: point, s: Field3, - P: Point, + G: Point, t: Field3, - table?: { - windowSize: number; // what we called c before - multiples?: point[]; // 0, G, 2*G, ..., (2^c-1)*G - } + P: Point, + { + // what we called c before + windowSizeG = 1, + // G, ..., (2^c-1)*G + multiplesG = undefined as Point[] | undefined, + windowSizeP = 1, + multiplesP = undefined as Point[] | undefined, + } = {} ): Point { - throw Error('TODO'); + // parse or build point tables + let Gs = getPointTable(Curve, G, windowSizeG, multiplesG); + let Ps = getPointTable(Curve, P, windowSizeP, multiplesP); + + // slice scalars + let b = Curve.order.toString(2).length; + let ss = slice(s, { maxBits: b, chunkSize: windowSizeG }); + let ts = slice(t, { maxBits: b, chunkSize: windowSizeP }); + + let sum = Point.from(ia); + + for (let i = 0; i < b; i++) { + if (i % windowSizeG === 0) { + // pick point to add based on the scalar chunk + let sj = ss[i / windowSizeG]; + let Gj = windowSizeG === 1 ? G : arrayGet(Point, Gs, sj, { offset: 1 }); + + // ec addition + let added = add(sum, Gj, Curve.p); + + // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) + sum = Provable.if(sj.equals(0), Point, sum, added); + } + + if (i % windowSizeP === 0) { + let tj = ts[i / windowSizeP]; + let Pj = windowSizeP === 1 ? P : arrayGet(Point, Ps, tj, { offset: 1 }); + let added = add(sum, Pj, Curve.p); + sum = Provable.if(tj.equals(0), Point, sum, added); + } + + // jointly double both points + sum = double(sum, Curve.p); + } + + // the sum is now s*G + t*P + 2^b*IA + // we assert that sum != 2^b*IA, and add -2^b*IA to get our result + let iaTimes2ToB = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b)); + Provable.equal(Point, sum, Point.from(iaTimes2ToB)).assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaTimes2ToB)), Curve.p); + + return sum; } /** @@ -251,6 +303,30 @@ function verifyEcdsaConstant( return mod(X.x, q) === r; } +function getPointTable( + Curve: CurveAffine, + P: Point, + windowSize: number, + table?: Point[] +): Point[] { + assertPositiveInteger(windowSize, 'invalid window size'); + let n = (1 << windowSize) - 1; // n >= 1 + + assert(table === undefined || table.length === n, 'invalid table'); + if (table !== undefined) return table; + + table = [P]; + if (n === 1) return table; + + let Pi = double(P, Curve.p); + table.push(Pi); + for (let i = 2; i < n; i++) { + Pi = add(Pi, P, Curve.p); + table.push(Pi); + } + return table; +} + /** * For EC scalar multiplication we use an initial point which is subtracted * at the end, to avoid encountering the point at infinity. @@ -271,12 +347,101 @@ function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { // increment x until we find a y coordinate while (y === undefined) { + x = F.add(x, 1n); // solve y^2 = x^3 + ax + b let x3 = F.mul(F.square(x), x); let y2 = F.add(x3, F.mul(a, x) + b); y = F.sqrt(y2); } - return { x: F.mod(x), y, infinity: false }; + return { x, y, infinity: false }; +} + +/** + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `windowSize` + * + * TODO: atm this uses expensive boolean checks for the bits. + * For larger chunks, we should use more efficient range checks. + * + * Note: This serves as a range check for the input limbs + */ +function slice( + [x0, x1, x2]: Field3, + { maxBits, chunkSize }: { maxBits: number; chunkSize: number } +) { + let l = Number(L); + + // first limb + let chunks0 = sliceField(x0, Math.min(l, maxBits), chunkSize); + if (maxBits <= l) return chunks0; + maxBits -= l; + + // second limb + let chunks1 = sliceField(x1, Math.min(l, maxBits), chunkSize); + if (maxBits <= l) return chunks0.concat(chunks1); + maxBits -= l; + + // third limb + let chunks2 = sliceField(x2, maxBits, chunkSize); + return chunks0.concat(chunks1).concat(chunks2); +} + +/** + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `windowSize` + * + * TODO: atm this uses expensive boolean checks for the bits. + * For larger chunks, we should use more efficient range checks. + * + * Note: This serves as a range check that the input is in [0, 2^k) where `k = ceil(maxBits / windowSize) * windowSize` + */ +function sliceField(x: Field, maxBits: number, chunkSize: number) { + let bits = exists(maxBits, () => { + let bits = bigIntToBits(x.toBigInt()); + // normalize length + if (bits.length > maxBits) bits = bits.slice(0, maxBits); + if (bits.length < maxBits) + bits = bits.concat(Array(maxBits - bits.length).fill(false)); + return bits.map(BigInt); + }); + + let chunks = []; + let sum = Field.from(0n); + for (let i = 0; i < maxBits; i += chunkSize) { + // prove that chunk has `windowSize` bits + // TODO: this inner sum should be replaced with a more efficient range check when possible + let chunk = Field.from(0n); + for (let j = 0; j < chunkSize; j++) { + let bit = bits[i + j]; + Bool.check(Bool.Unsafe.ofField(bit)); + chunk = chunk.add(bit.mul(1n << BigInt(j))); + } + chunk = chunk.seal(); + // prove that chunks add up to x + sum = sum.add(chunk.mul(1n << BigInt(i))); + chunks.push(chunk); + } + sum.assertEquals(x); + + return chunks; +} + +/** + * Get value from array in O(n) constraints. + * + * If the index is out of bounds, returns all-zeros version of T + */ +function arrayGet( + type: Provable, + array: T[], + index: Field, + { offset = 0 } = {} +) { + let n = array.length; + let oneHot = Array(n); + // TODO can we share computation between all those equals()? + for (let i = 0; i < n; i++) { + oneHot[i] = index.equals(i + offset); + } + return Provable.switch(oneHot, type, array); } const Point = { @@ -296,6 +461,16 @@ const Signature = { }, }; +function gcd(a: number, b: number) { + if (b > a) [a, b] = [b, a]; + while (true) { + if (b === 0) return a; + [a, b] = [b, a % b]; + } +} + +console.log(gcd(2, 4)); + let csAdd = Provable.constraintSystem(() => { let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); From c39e0f8b442883831e9769af85daeb5bd6ad978e Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 14:05:41 +0100 Subject: [PATCH 0680/1215] signature from hex --- src/lib/gadgets/elliptic-curve.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 3f24a8a346..5179dcf1a8 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -250,7 +250,7 @@ function doubleScalarMul( let Gj = windowSizeG === 1 ? G : arrayGet(Point, Gs, sj, { offset: 1 }); // ec addition - let added = add(sum, Gj, Curve.p); + let added = add(sum, Gj, Curve.modulus); // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) sum = Provable.if(sj.equals(0), Point, sum, added); @@ -259,19 +259,19 @@ function doubleScalarMul( if (i % windowSizeP === 0) { let tj = ts[i / windowSizeP]; let Pj = windowSizeP === 1 ? P : arrayGet(Point, Ps, tj, { offset: 1 }); - let added = add(sum, Pj, Curve.p); + let added = add(sum, Pj, Curve.modulus); sum = Provable.if(tj.equals(0), Point, sum, added); } // jointly double both points - sum = double(sum, Curve.p); + sum = double(sum, Curve.modulus); } // the sum is now s*G + t*P + 2^b*IA // we assert that sum != 2^b*IA, and add -2^b*IA to get our result let iaTimes2ToB = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b)); Provable.equal(Point, sum, Point.from(iaTimes2ToB)).assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaTimes2ToB)), Curve.p); + sum = add(sum, Point.from(Curve.negate(iaTimes2ToB)), Curve.modulus); return sum; } @@ -318,10 +318,10 @@ function getPointTable( table = [P]; if (n === 1) return table; - let Pi = double(P, Curve.p); + let Pi = double(P, Curve.modulus); table.push(Pi); for (let i = 2; i < n; i++) { - Pi = add(Pi, P, Curve.p); + Pi = add(Pi, P, Curve.modulus); table.push(Pi); } return table; @@ -456,9 +456,28 @@ const Point = { const Signature = { ...provable({ r: Field3.provable, s: Field3.provable }), + from({ r, s }: signature): Signature { + return { r: Field3.from(r), s: Field3.from(s) }; + }, toBigint({ r, s }: Signature): signature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, + /** + * Create a {@link Signature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ + fromHex(rawSignature: string): Signature { + let prefix = rawSignature.slice(0, 2); + let signature = rawSignature.slice(2, 130); + if (prefix !== '0x' || signature.length < 128) { + throw Error( + `Signature.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` + ); + } + let r = BigInt(`0x${signature.slice(0, 64)}`); + let s = BigInt(`0x${signature.slice(64)}`); + return Signature.from({ r, s }); + }, }; function gcd(a: number, b: number) { From b924cad9d7525bced296161d6d9f5f6447bac931 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 14:22:40 +0100 Subject: [PATCH 0681/1215] add ecdsa test script --- src/lib/gadgets/ecdsa.unit-test.ts | 54 ++++++++++++++++++++++++++++++ src/lib/gadgets/elliptic-curve.ts | 35 +++++++++++++------ 2 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 src/lib/gadgets/ecdsa.unit-test.ts diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts new file mode 100644 index 0000000000..80c657bcdf --- /dev/null +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -0,0 +1,54 @@ +import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; +import { Ecdsa, EllipticCurve, Point } from './elliptic-curve.js'; +import { Field3 } from './foreign-field.js'; +import { secp256k1Params } from '../../bindings/crypto/elliptic-curve-examples.js'; +import { Provable } from '../provable.js'; +import { createField } from '../../bindings/crypto/finite_field.js'; + +const Secp256k1 = createCurveAffine(secp256k1Params); +const BaseField = createField(secp256k1Params.modulus); + +let publicKey = Point.from({ + x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, + y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n, +}); + +let signature = Ecdsa.Signature.fromHex( + '0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b' +); + +let msgHash = + Field3.from( + 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn + ); + +const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); + +function main() { + let signature0 = Provable.witness(Ecdsa.Signature, () => signature); + Ecdsa.verify(Secp256k1, ia, signature0, msgHash, publicKey, { + windowSizeG: 3, + windowSizeP: 3, + }); +} + +console.time('ecdsa verify (constant)'); +main(); +console.timeEnd('ecdsa verify (constant)'); + +console.time('ecdsa verify (witness gen / check)'); +Provable.runAndCheck(main); +console.timeEnd('ecdsa verify (witness gen / check)'); + +console.time('ecdsa verify (build constraint system)'); +let cs = Provable.constraintSystem(main); +console.timeEnd('ecdsa verify (build constraint system)'); + +let gateTypes: Record = {}; +gateTypes['Total rows'] = cs.rows; +for (let gate of cs.gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; +} + +console.log(gateTypes); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 5179dcf1a8..a2911386e1 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -32,6 +32,14 @@ import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; +export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; + +const EllipticCurve = { + add, + double, + initialAggregator, +}; + /** * Non-zero elliptic curve point in affine coordinates. */ @@ -41,8 +49,8 @@ type point = { x: bigint; y: bigint }; /** * ECDSA signature consisting of two curve scalars. */ -type Signature = { r: Field3; s: Field3 }; -type signature = { r: bigint; s: bigint }; +type EcdsaSignature = { r: Field3; s: Field3 }; +type ecdsaSignature = { r: bigint; s: bigint }; function add(p1: Point, p2: Point, f: bigint) { let { x: x1, y: y1 } = p1; @@ -160,7 +168,7 @@ function double(p1: Point, f: bigint) { function verifyEcdsa( Curve: CurveAffine, ia: point, - signature: Signature, + signature: EcdsaSignature, msgHash: Field3, publicKey: Point, tables?: { @@ -172,13 +180,13 @@ function verifyEcdsa( ) { // constant case if ( - Provable.isConstant(Signature, signature) && + Provable.isConstant(EcdsaSignature, signature) && Field3.isConstant(msgHash) && Provable.isConstant(Point, publicKey) ) { let isValid = verifyEcdsaConstant( Curve, - Signature.toBigint(signature), + EcdsaSignature.toBigint(signature), Field3.toBigint(msgHash), Point.toBigint(publicKey) ); @@ -454,19 +462,19 @@ const Point = { }, }; -const Signature = { +const EcdsaSignature = { ...provable({ r: Field3.provable, s: Field3.provable }), - from({ r, s }: signature): Signature { + from({ r, s }: ecdsaSignature): EcdsaSignature { return { r: Field3.from(r), s: Field3.from(s) }; }, - toBigint({ r, s }: Signature): signature { + toBigint({ r, s }: EcdsaSignature): ecdsaSignature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, /** - * Create a {@link Signature} from a raw 130-char hex string as used in + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). */ - fromHex(rawSignature: string): Signature { + fromHex(rawSignature: string): EcdsaSignature { let prefix = rawSignature.slice(0, 2); let signature = rawSignature.slice(2, 130); if (prefix !== '0x' || signature.length < 128) { @@ -476,10 +484,15 @@ const Signature = { } let r = BigInt(`0x${signature.slice(0, 64)}`); let s = BigInt(`0x${signature.slice(64)}`); - return Signature.from({ r, s }); + return EcdsaSignature.from({ r, s }); }, }; +const Ecdsa = { + verify: verifyEcdsa, + Signature: EcdsaSignature, +}; + function gcd(a: number, b: number) { if (b > a) [a, b] = [b, a]; while (true) { From 9a2629f576e2b69ee2ee18d78c65484eecad1f15 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 14:22:55 +0100 Subject: [PATCH 0682/1215] move initial ec testing code --- src/lib/gadgets/elliptic-curve.ts | 33 ----------------- src/lib/gadgets/elliptic-curve.unit-test.ts | 40 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 src/lib/gadgets/elliptic-curve.unit-test.ts diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index a2911386e1..001137f974 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -500,36 +500,3 @@ function gcd(a: number, b: number) { [a, b] = [b, a % b]; } } - -console.log(gcd(2, 4)); - -let csAdd = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - - let g = { x: x1, y: y1 }; - let h = { x: x2, y: y2 }; - - add(g, h, exampleFields.secp256k1.modulus); -}); - -let csDouble = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - - let g = { x: x1, y: y1 }; - - double(g, exampleFields.secp256k1.modulus); -}); - -printGates(csAdd.gates); -console.log({ digest: csAdd.digest, rows: csAdd.rows }); - -printGates(csDouble.gates); -console.log({ digest: csDouble.digest, rows: csDouble.rows }); - -let point = initialAggregator(exampleFields.Fp, { a: 0n, b: 5n }); -console.log({ point }); -assert(Pallas.isOnCurve(Pallas.fromAffine(point))); diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts new file mode 100644 index 0000000000..c595011a2e --- /dev/null +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -0,0 +1,40 @@ +import { exampleFields } from 'src/bindings/crypto/finite-field-examples.js'; +import { Provable } from '../provable.js'; +import { Field3 } from './foreign-field.js'; +import { EllipticCurve } from './elliptic-curve.js'; +import { printGates } from '../testing/constraint-system.js'; +import { assert } from './common.js'; +import { Pallas } from '../../bindings/crypto/elliptic_curve.js'; + +let { add, double, initialAggregator } = EllipticCurve; + +let csAdd = Provable.constraintSystem(() => { + let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y2 = Provable.witness(Field3.provable, () => Field3.from(0n)); + + let g = { x: x1, y: y1 }; + let h = { x: x2, y: y2 }; + + add(g, h, exampleFields.secp256k1.modulus); +}); + +let csDouble = Provable.constraintSystem(() => { + let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); + + let g = { x: x1, y: y1 }; + + double(g, exampleFields.secp256k1.modulus); +}); + +printGates(csAdd.gates); +console.log({ digest: csAdd.digest, rows: csAdd.rows }); + +printGates(csDouble.gates); +console.log({ digest: csDouble.digest, rows: csDouble.rows }); + +let point = initialAggregator(exampleFields.Fp, { a: 0n, b: 5n }); +console.log({ point }); +assert(Pallas.isOnCurve(Pallas.fromAffine(point))); From a1370e8c7cd5175e2d652b6072bd84bc6d1f4dd3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 14:31:02 +0100 Subject: [PATCH 0683/1215] fix bit slicing logic --- src/lib/gadgets/elliptic-curve.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 001137f974..7f5da5ad17 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -399,7 +399,7 @@ function slice( * TODO: atm this uses expensive boolean checks for the bits. * For larger chunks, we should use more efficient range checks. * - * Note: This serves as a range check that the input is in [0, 2^k) where `k = ceil(maxBits / windowSize) * windowSize` + * Note: This serves as a range check that the input is in [0, 2^maxBits) */ function sliceField(x: Field, maxBits: number, chunkSize: number) { let bits = exists(maxBits, () => { @@ -413,11 +413,13 @@ function sliceField(x: Field, maxBits: number, chunkSize: number) { let chunks = []; let sum = Field.from(0n); + for (let i = 0; i < maxBits; i += chunkSize) { - // prove that chunk has `windowSize` bits + // prove that chunk has `chunkSize` bits // TODO: this inner sum should be replaced with a more efficient range check when possible let chunk = Field.from(0n); - for (let j = 0; j < chunkSize; j++) { + let size = Math.min(maxBits - i, chunkSize); // last chunk might be smaller + for (let j = 0; j < size; j++) { let bit = bits[i + j]; Bool.check(Bool.Unsafe.ofField(bit)); chunk = chunk.add(bit.mul(1n << BigInt(j))); From 92318feea8d1b2234cbebe135aa6d23e365da912 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 14:39:40 +0100 Subject: [PATCH 0684/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 0c9a907ca5..8f0e0d0f87 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 0c9a907ca5dad975b6677e3f7d5d3f86bb9e6fdc +Subproject commit 8f0e0d0f874dc3952bb01791c4b31394d7e5298f From 92a627ed49827256d88bba1dd024589f7bafca76 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 17:47:33 +0100 Subject: [PATCH 0685/1215] several fixes to the scaling algorithm, debugging --- src/lib/gadgets/ecdsa.unit-test.ts | 57 ++++++++++++++++++++++++++---- src/lib/gadgets/elliptic-curve.ts | 40 ++++++++++++++++----- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 80c657bcdf..6707b84c80 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -4,6 +4,7 @@ import { Field3 } from './foreign-field.js'; import { secp256k1Params } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { createField } from '../../bindings/crypto/finite_field.js'; +import { ZkProgram } from '../proof_system.js'; const Secp256k1 = createCurveAffine(secp256k1Params); const BaseField = createField(secp256k1Params.modulus); @@ -23,14 +24,48 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); +// TODO doesn't work with windowSize = 3 +const tableConfig = { windowSizeG: 2, windowSizeP: 2 }; -function main() { - let signature0 = Provable.witness(Ecdsa.Signature, () => signature); - Ecdsa.verify(Secp256k1, ia, signature0, msgHash, publicKey, { - windowSizeG: 3, - windowSizeP: 3, - }); -} +let program = ZkProgram({ + name: 'ecdsa', + methods: { + scale: { + privateInputs: [], + method() { + let G = Point.from(Secp256k1.one); + let P = Provable.witness(Point, () => publicKey); + let R = EllipticCurve.doubleScalarMul( + Secp256k1, + ia, + signature.s, + G, + signature.r, + P, + tableConfig + ); + Provable.asProver(() => { + console.log(Point.toBigint(R)); + }); + }, + }, + ecdsa: { + privateInputs: [], + method() { + let signature0 = Provable.witness(Ecdsa.Signature, () => signature); + Ecdsa.verify( + Secp256k1, + ia, + signature0, + msgHash, + publicKey, + tableConfig + ); + }, + }, + }, +}); +let main = program.rawMethods.ecdsa; console.time('ecdsa verify (constant)'); main(); @@ -52,3 +87,11 @@ for (let gate of cs.gates) { } console.log(gateTypes); + +console.time('ecdsa verify (compile)'); +await program.compile(); +console.timeEnd('ecdsa verify (compile)'); + +console.time('ecdsa verify (prove)'); +let proof = await program.ecdsa(); +console.timeEnd('ecdsa verify (prove)'); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 7f5da5ad17..1294ef9bb2 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -3,7 +3,6 @@ import { inverse, mod, } from '../../bindings/crypto/finite_field.js'; -import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { assert, exists } from './common.js'; @@ -16,7 +15,6 @@ import { weakBound, } from './foreign-field.js'; import { L, multiRangeCheck } from './range-check.js'; -import { printGates } from '../testing/constraint-system.js'; import { sha256 } from 'js-sha256'; import { bigIntToBits, @@ -24,7 +22,6 @@ import { } from '../../bindings/crypto/bigint-helpers.js'; import { CurveAffine, - Pallas, affineAdd, affineDouble, } from '../../bindings/crypto/elliptic_curve.js'; @@ -37,6 +34,7 @@ export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; const EllipticCurve = { add, double, + doubleScalarMul, initialAggregator, }; @@ -207,6 +205,11 @@ function verifyEcdsa( // reduce R.x modulo the curve order let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); + Provable.asProver(() => { + let [u1_, u2_, Rx_, r_] = Field3.toBigints(u1, u2, Rx, r); + let R_ = Point.toBigint(R); + console.log({ u1_, u2_, R_, Rx_, r_ }); + }); Provable.assertEqual(Field3.provable, Rx, r); } @@ -240,6 +243,21 @@ function doubleScalarMul( multiplesP = undefined as Point[] | undefined, } = {} ): Point { + // constant case + if ( + Field3.isConstant(s) && + Field3.isConstant(t) && + Provable.isConstant(Point, G) && + Provable.isConstant(Point, P) + ) { + let s_ = Field3.toBigint(s); + let t_ = Field3.toBigint(t); + let G_ = Point.toBigint(G); + let P_ = Point.toBigint(P); + let R = Curve.add(Curve.scale(G_, s_), Curve.scale(P_, t_)); + return Point.from(R); + } + // parse or build point tables let Gs = getPointTable(Curve, G, windowSizeG, multiplesG); let Ps = getPointTable(Curve, P, windowSizeP, multiplesP); @@ -249,9 +267,11 @@ function doubleScalarMul( let ss = slice(s, { maxBits: b, chunkSize: windowSizeG }); let ts = slice(t, { maxBits: b, chunkSize: windowSizeP }); + console.log({ b, windowSizeG, windowSizeP, ss: ss.length, ts: ts.length }); + let sum = Point.from(ia); - for (let i = 0; i < b; i++) { + for (let i = b - 1; i >= 0; i--) { if (i % windowSizeG === 0) { // pick point to add based on the scalar chunk let sj = ss[i / windowSizeG]; @@ -270,16 +290,17 @@ function doubleScalarMul( let added = add(sum, Pj, Curve.modulus); sum = Provable.if(tj.equals(0), Point, sum, added); } + if (i === 0) break; // jointly double both points sum = double(sum, Curve.modulus); } - // the sum is now s*G + t*P + 2^b*IA - // we assert that sum != 2^b*IA, and add -2^b*IA to get our result - let iaTimes2ToB = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b)); - Provable.equal(Point, sum, Point.from(iaTimes2ToB)).assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaTimes2ToB)), Curve.modulus); + // the sum is now s*G + t*P + 2^(b-1)*IA + // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result + let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); + Provable.equal(Point, sum, Point.from(iaFinal)).assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); return sum; } @@ -306,6 +327,7 @@ function verifyEcdsaConstant( let u2 = mod(r * sInv, q); let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); + console.log({ u1, u2, R: X, Rx: mod(X.x, q), r }); if (Curve.equal(X, Curve.zero)) return false; return mod(X.x, q) === r; From cc4f9b12a96a2c067ec56b21f314395d008e6931 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 21:46:20 +0100 Subject: [PATCH 0686/1215] fix uneven window sizes by glueing together overlapping chunk between limbs --- src/lib/gadgets/ecdsa.unit-test.ts | 5 +++- src/lib/gadgets/elliptic-curve.ts | 46 +++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 6707b84c80..5eeab04be2 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -5,6 +5,7 @@ import { secp256k1Params } from '../../bindings/crypto/elliptic-curve-examples.j import { Provable } from '../provable.js'; import { createField } from '../../bindings/crypto/finite_field.js'; import { ZkProgram } from '../proof_system.js'; +import { assert } from './common.js'; const Secp256k1 = createCurveAffine(secp256k1Params); const BaseField = createField(secp256k1Params.modulus); @@ -25,7 +26,7 @@ let msgHash = const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); // TODO doesn't work with windowSize = 3 -const tableConfig = { windowSizeG: 2, windowSizeP: 2 }; +const tableConfig = { windowSizeG: 3, windowSizeP: 3 }; let program = ZkProgram({ name: 'ecdsa', @@ -95,3 +96,5 @@ console.timeEnd('ecdsa verify (compile)'); console.time('ecdsa verify (prove)'); let proof = await program.ecdsa(); console.timeEnd('ecdsa verify (prove)'); + +assert(await program.verify(proof), 'proof verifies'); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 1294ef9bb2..c8f205dc99 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -221,7 +221,7 @@ function verifyEcdsa( * where G, P are any points. The result is not allowed to be zero. * * We double both points together and leverage a precomputed table - * of size 2^c to avoid all but every cth addition for t*G. + * of size 2^c to avoid all but every cth addition for both s*G and t*P. * * TODO: could use lookups for picking precomputed multiples, instead of O(2^c) provable switch * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case @@ -267,7 +267,7 @@ function doubleScalarMul( let ss = slice(s, { maxBits: b, chunkSize: windowSizeG }); let ts = slice(t, { maxBits: b, chunkSize: windowSizeP }); - console.log({ b, windowSizeG, windowSizeP, ss: ss.length, ts: ts.length }); + console.log({ b, windowSizeG, ss: ss.length }); let sum = Point.from(ia); @@ -399,20 +399,21 @@ function slice( { maxBits, chunkSize }: { maxBits: number; chunkSize: number } ) { let l = Number(L); + assert(maxBits <= 3 * l, `expected max bits <= 3*${l}, got ${maxBits}`); // first limb - let chunks0 = sliceField(x0, Math.min(l, maxBits), chunkSize); - if (maxBits <= l) return chunks0; + let result0 = sliceField(x0, Math.min(l, maxBits), chunkSize); + if (maxBits <= l) return result0.chunks; maxBits -= l; // second limb - let chunks1 = sliceField(x1, Math.min(l, maxBits), chunkSize); - if (maxBits <= l) return chunks0.concat(chunks1); + let result1 = sliceField(x1, Math.min(l, maxBits), chunkSize, result0); + if (maxBits <= l) return result0.chunks.concat(result1.chunks); maxBits -= l; // third limb - let chunks2 = sliceField(x2, maxBits, chunkSize); - return chunks0.concat(chunks1).concat(chunks2); + let result2 = sliceField(x2, maxBits, chunkSize, result1); + return result0.chunks.concat(result1.chunks, result2.chunks); } /** @@ -423,7 +424,12 @@ function slice( * * Note: This serves as a range check that the input is in [0, 2^maxBits) */ -function sliceField(x: Field, maxBits: number, chunkSize: number) { +function sliceField( + x: Field, + maxBits: number, + chunkSize: number, + leftover?: { chunks: Field[]; leftoverSize: number } +) { let bits = exists(maxBits, () => { let bits = bigIntToBits(x.toBigInt()); // normalize length @@ -436,7 +442,24 @@ function sliceField(x: Field, maxBits: number, chunkSize: number) { let chunks = []; let sum = Field.from(0n); - for (let i = 0; i < maxBits; i += chunkSize) { + // if there's a leftover chunk from a previous slizeField() call, we complete it + if (leftover !== undefined) { + let { chunks: previous, leftoverSize: size } = leftover; + let remainingChunk = Field.from(0n); + for (let i = 0; i < size; i++) { + let bit = bits[i]; + Bool.check(Bool.Unsafe.ofField(bit)); + remainingChunk = remainingChunk.add(bit.mul(1n << BigInt(i))); + } + sum = remainingChunk = remainingChunk.seal(); + let chunk = previous[previous.length - 1]; + previous[previous.length - 1] = chunk.add( + remainingChunk.mul(1n << BigInt(chunkSize - size)) + ); + } + + let i = leftover?.leftoverSize ?? 0; + for (; i < maxBits; i += chunkSize) { // prove that chunk has `chunkSize` bits // TODO: this inner sum should be replaced with a more efficient range check when possible let chunk = Field.from(0n); @@ -453,7 +476,8 @@ function sliceField(x: Field, maxBits: number, chunkSize: number) { } sum.assertEquals(x); - return chunks; + let leftoverSize = i - maxBits; + return { chunks, leftoverSize } as const; } /** From 90d341fca76224e9fc12b38412ba57152216f648 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 21:53:54 +0100 Subject: [PATCH 0687/1215] remove debug logs --- src/lib/gadgets/ecdsa.unit-test.ts | 3 +-- src/lib/gadgets/elliptic-curve.ts | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 5eeab04be2..ea25a1e5ee 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,8 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); -// TODO doesn't work with windowSize = 3 -const tableConfig = { windowSizeG: 3, windowSizeP: 3 }; +const tableConfig = { windowSizeG: 5, windowSizeP: 3 }; let program = ZkProgram({ name: 'ecdsa', diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index c8f205dc99..97addcc3ae 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -205,11 +205,6 @@ function verifyEcdsa( // reduce R.x modulo the curve order let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); - Provable.asProver(() => { - let [u1_, u2_, Rx_, r_] = Field3.toBigints(u1, u2, Rx, r); - let R_ = Point.toBigint(R); - console.log({ u1_, u2_, R_, Rx_, r_ }); - }); Provable.assertEqual(Field3.provable, Rx, r); } @@ -267,8 +262,6 @@ function doubleScalarMul( let ss = slice(s, { maxBits: b, chunkSize: windowSizeG }); let ts = slice(t, { maxBits: b, chunkSize: windowSizeP }); - console.log({ b, windowSizeG, ss: ss.length }); - let sum = Point.from(ia); for (let i = b - 1; i >= 0; i--) { @@ -327,7 +320,6 @@ function verifyEcdsaConstant( let u2 = mod(r * sInv, q); let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); - console.log({ u1, u2, R: X, Rx: mod(X.x, q), r }); if (Curve.equal(X, Curve.zero)) return false; return mod(X.x, q) === r; From e7b8d235fbb9296e82261712fb2871ba285f2610 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 16 Nov 2023 21:56:49 +0100 Subject: [PATCH 0688/1215] fixup --- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index ea25a1e5ee..429d4c1248 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); -const tableConfig = { windowSizeG: 5, windowSizeP: 3 }; +const tableConfig = { windowSizeG: 3, windowSizeP: 3 }; let program = ZkProgram({ name: 'ecdsa', From f7c5176eb0230a09ed188b750bacdd343ae30f6a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 09:06:16 +0100 Subject: [PATCH 0689/1215] fix --- src/lib/gadgets/elliptic-curve.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index c595011a2e..b851590e14 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -1,4 +1,4 @@ -import { exampleFields } from 'src/bindings/crypto/finite-field-examples.js'; +import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { Provable } from '../provable.js'; import { Field3 } from './foreign-field.js'; import { EllipticCurve } from './elliptic-curve.js'; From 0ccea11d1bbc83f138a661d828b07572befa5b33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 10:25:32 +0100 Subject: [PATCH 0690/1215] fix: don't range-check 2-bit carry --- src/lib/gadgets/foreign-field.ts | 40 ++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index c8415be2dc..8d5d2edbe1 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -117,16 +117,16 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { } // provable case - let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(a, b, f); + let { r01, r2, q } = multiplyNoRangeCheck(a, b, f); // limb range checks on quotient and remainder multiRangeCheck(q); let r = compactMultiRangeCheck(r01, r2); - // range check on q and r bounds - // TODO: this uses one RC too many.. need global RC stack, or get rid of bounds checks + // range check on r bound + // TODO: this uses two RCs too many.. need global RC stack, or get rid of bounds checks let r2Bound = weakBound(r2, f); - multiRangeCheck([q2Bound, r2Bound, Field.from(0n)]); + multiRangeCheck([r2Bound, Field.from(0n), Field.from(0n)]); return r; } @@ -150,11 +150,11 @@ function inverse(x: Field3, f: bigint): Field3 { let xInv2Bound = weakBound(xInv[2], f); let one: Field2 = [Field.from(1n), Field.from(0n)]; - let q2Bound = assertMul(x, xInv, one, f); + assertMul(x, xInv, one, f); - // range check on q and result bounds - // TODO: this uses one RC too many.. need global RC stack - multiRangeCheck([q2Bound, xInv2Bound, Field.from(0n)]); + // range check on result bound + // TODO: this uses two RCs too many.. need global RC stack + multiRangeCheck([xInv2Bound, Field.from(0n), Field.from(0n)]); return xInv; } @@ -183,10 +183,10 @@ function divide( }); multiRangeCheck(z); let z2Bound = weakBound(z[2], f); - let q2Bound = assertMul(z, y, x, f); + assertMul(z, y, x, f); - // range check on q and result bounds - multiRangeCheck([q2Bound, z2Bound, Field.from(0n)]); + // range check on result bound + multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); if (!allowZeroOverZero) { // assert that y != 0 mod f by checking that it doesn't equal 0 or f @@ -203,10 +203,10 @@ function divide( } /** - * Common logic for gadgets that expect a certain multiplication result instead of just using the remainder. + * Common logic for gadgets that expect a certain multiplication result a priori, instead of just using the remainder. */ function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { - let { r01, r2, q, q2Bound } = multiplyNoRangeCheck(x, y, f); + let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); // range check on quotient multiRangeCheck(q); @@ -221,9 +221,11 @@ function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { r01.assertEquals(xy01); r2.assertEquals(xy[2]); } - return q2Bound; } +/** + * Core building block for all gadgets using foreign field multiplication. + */ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md let f_ = (1n << L3) - f; @@ -315,10 +317,14 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { negForeignFieldModulus: [f_0, f_1, f_2], }); - // multi-range check on intermediate values - multiRangeCheck([c0, p10, p110]); + // multi-range check on internal values + multiRangeCheck([p10, p110, q2Bound]); - return { r01, r2, q, q2Bound }; + // note: this function is supposed to be the most flexible interface to the ffmul gate. + // that's why we don't add range checks on q and r here, because there are valid use cases + // for not range-checking either of them -- for example, they could be wired to other + // variables that are already range-checked, or to constants / public inputs. + return { r01, r2, q }; } function weakBound(x: Field, f: bigint) { From 571866b35488fd0fc85077bd7f986556e7761a11 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 10:29:39 +0100 Subject: [PATCH 0691/1215] consistent lower-casing of limb size constant --- src/lib/gadgets/foreign-field.ts | 32 ++++++++++++------------ src/lib/gadgets/range-check.ts | 22 ++++++++-------- src/lib/gadgets/range-check.unit-test.ts | 12 ++++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 8d5d2edbe1..177b16c5cd 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -8,12 +8,12 @@ import { Gates, foreignFieldAdd } from '../gates.js'; import { Tuple } from '../util/types.js'; import { assert, bitSlice, exists, toVars } from './common.js'; import { - L, + l, lMask, multiRangeCheck, - L2, + l2, l2Mask, - L3, + l3, compactMultiRangeCheck, } from './range-check.js'; @@ -94,7 +94,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { // do the add with carry // note: this "just works" with negative r01 let r01 = collapse2(x_) + sign * collapse2(y_) - overflow * collapse2(f_); - let carry = r01 >> L2; + let carry = r01 >> l2; r01 &= l2Mask; let [r0, r1] = split2(r01); let r2 = x_[2] + sign * y_[2] - overflow * f_[2] + carry; @@ -192,7 +192,7 @@ function divide( // assert that y != 0 mod f by checking that it doesn't equal 0 or f // this works because we assume y[2] <= f2 // TODO is this the most efficient way? - let y01 = y[0].add(y[1].mul(1n << L)); + let y01 = y[0].add(y[1].mul(1n << l)); y01.equals(0n).and(y[2].equals(0n)).assertFalse(); let [f0, f1, f2] = split(f); let f01 = collapse2([f0, f1]); @@ -217,7 +217,7 @@ function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { r01.assertEquals(xy01); r2.assertEquals(xy2); } else { - let xy01 = xy[0].add(xy[1].mul(1n << L)); + let xy01 = xy[0].add(xy[1].mul(1n << l)); r01.assertEquals(xy01); r2.assertEquals(xy[2]); } @@ -228,10 +228,10 @@ function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { */ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { // notation follows https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md - let f_ = (1n << L3) - f; + let f_ = (1n << l3) - f; let [f_0, f_1, f_2] = split(f_); - let f2 = f >> L2; - let f2Bound = (1n << L) - f2 - 1n; + let f2 = f >> l2; + let f2Bound = (1n << l) - f2 - 1n; let witnesses = exists(21, () => { // split inputs into 3 limbs @@ -256,10 +256,10 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { let p11 = collapse2([p110, p111]); // carry bottom limbs - let c0 = (p0 + (p10 << L) - r01) >> L2; + let c0 = (p0 + (p10 << l) - r01) >> l2; // carry top limb - let c1 = (p2 - r2 + p11 + c0) >> L; + let c1 = (p2 - r2 + p11 + c0) >> l; // split high carry let c1_00 = bitSlice(c1, 0, 12); @@ -328,7 +328,7 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { } function weakBound(x: Field, f: bigint) { - return x.add(lMask - (f >> L2)); + return x.add(lMask - (f >> l2)); } const Field3 = { @@ -367,15 +367,15 @@ function bigint3(x: Field3): bigint3 { } function collapse([x0, x1, x2]: bigint3) { - return x0 + (x1 << L) + (x2 << L2); + return x0 + (x1 << l) + (x2 << l2); } function split(x: bigint): bigint3 { - return [x & lMask, (x >> L) & lMask, (x >> L2) & lMask]; + return [x & lMask, (x >> l) & lMask, (x >> l2) & lMask]; } function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { - return x0 + (x1 << L); + return x0 + (x1 << l); } function split2(x: bigint): [bigint, bigint] { - return [x & lMask, (x >> L) & lMask]; + return [x & lMask, (x >> l) & lMask]; } diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 7abfe07394..1e44afdaf9 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -3,7 +3,7 @@ import { Gates } from '../gates.js'; import { bitSlice, exists, toVar, toVars } from './common.js'; export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; -export { L, L2, L3, lMask, l2Mask }; +export { l, l2, l3, lMask, l2Mask }; /** * Asserts that x is in the range [0, 2^64) @@ -51,19 +51,19 @@ function rangeCheck64(x: Field) { } // default bigint limb size -const L = 88n; -const L2 = 2n * L; -const L3 = 3n * L; -const lMask = (1n << L) - 1n; -const l2Mask = (1n << L2) - 1n; +const l = 88n; +const l2 = 2n * l; +const l3 = 3n * l; +const lMask = (1n << l) - 1n; +const l2Mask = (1n << l2) - 1n; /** * Asserts that x, y, z \in [0, 2^88) */ function multiRangeCheck([x, y, z]: [Field, Field, Field]) { if (x.isConstant() && y.isConstant() && z.isConstant()) { - if (x.toBigInt() >> L || y.toBigInt() >> L || z.toBigInt() >> L) { - throw Error(`Expected fields to fit in ${L} bits, got ${x}, ${y}, ${z}`); + if (x.toBigInt() >> l || y.toBigInt() >> l || z.toBigInt() >> l) { + throw Error(`Expected fields to fit in ${l} bits, got ${x}, ${y}, ${z}`); } return; } @@ -86,9 +86,9 @@ function multiRangeCheck([x, y, z]: [Field, Field, Field]) { function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { // constant case if (xy.isConstant() && z.isConstant()) { - if (xy.toBigInt() >> L2 || z.toBigInt() >> L) { + if (xy.toBigInt() >> l2 || z.toBigInt() >> l) { throw Error( - `Expected fields to fit in ${L2} and ${L} bits respectively, got ${xy}, ${z}` + `Expected fields to fit in ${l2} and ${l} bits respectively, got ${xy}, ${z}` ); } let [x, y] = splitCompactLimb(xy.toBigInt()); @@ -107,7 +107,7 @@ function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { } function splitCompactLimb(x01: bigint): [bigint, bigint] { - return [x01 & lMask, x01 >> L]; + return [x01 & lMask, x01 >> l]; } function rangeCheck0Helper(x: Field, isCompact = false): [Field, Field] { diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index b5d5110807..47aafbf592 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -10,7 +10,7 @@ import { import { Random } from '../testing/property.js'; import { assert } from './common.js'; import { Gadgets } from './gadgets.js'; -import { L } from './range-check.js'; +import { l } from './range-check.js'; import { constraintSystem, contains, @@ -82,7 +82,7 @@ let RangeCheck = ZkProgram({ privateInputs: [Field, Field], method(xy, z) { let [x, y] = Gadgets.compactMultiRangeCheck(xy, z); - x.add(y.mul(1n << L)).assertEquals(xy); + x.add(y.mul(1n << l)).assertEquals(xy); }, }, }, @@ -104,11 +104,11 @@ await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( ); await equivalentAsync( - { from: [maybeUint(L), uint(L), uint(L)], to: boolean }, + { from: [maybeUint(l), uint(l), uint(l)], to: boolean }, { runs: 3 } )( (x, y, z) => { - assert(!(x >> L) && !(y >> L) && !(z >> L), 'multi: not out of range'); + assert(!(x >> l) && !(y >> l) && !(z >> l), 'multi: not out of range'); return true; }, async (x, y, z) => { @@ -118,11 +118,11 @@ await equivalentAsync( ); await equivalentAsync( - { from: [maybeUint(2n * L), uint(L)], to: boolean }, + { from: [maybeUint(2n * l), uint(l)], to: boolean }, { runs: 3 } )( (xy, z) => { - assert(!(xy >> (2n * L)) && !(z >> L), 'compact: not out of range'); + assert(!(xy >> (2n * l)) && !(z >> l), 'compact: not out of range'); return true; }, async (xy, z) => { From 968a5aec86b70426f960f787a332097f11a8999c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 10:47:33 +0100 Subject: [PATCH 0692/1215] adapt to ffmul changes --- src/lib/gadgets/elliptic-curve.ts | 35 +++++++++++++++---------------- src/lib/gadgets/foreign-field.ts | 4 +--- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 97addcc3ae..89494d47e2 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -14,7 +14,7 @@ import { split, weakBound, } from './foreign-field.js'; -import { L, multiRangeCheck } from './range-check.js'; +import { l, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; import { bigIntToBits, @@ -87,20 +87,19 @@ function add(p1: Point, p2: Point, f: bigint) { // (x1 - x2)*m = y1 - y2 let deltaX = new Sum(x1).sub(x2); let deltaY = new Sum(y1).sub(y2); - let qBound1 = assertRank1(deltaX, m, deltaY, f); + assertRank1(deltaX, m, deltaY, f); // m^2 = x1 + x2 + x3 let xSum = new Sum(x1).add(x2).add(x3); - let qBound2 = assertRank1(m, m, xSum, f); + assertRank1(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 let deltaX1X3 = new Sum(x1).sub(x3); let ySum = new Sum(y1).add(y3); - let qBound3 = assertRank1(deltaX1X3, m, ySum, f); + assertRank1(deltaX1X3, m, ySum, f); // bounds checks - multiRangeCheck([mBound, x3Bound, qBound1]); - multiRangeCheck([qBound2, qBound3, Field.from(0n)]); + multiRangeCheck([mBound, x3Bound, Field.from(0n)]); return { x: x3, y: y3 }; } @@ -145,20 +144,20 @@ function double(p1: Point, f: bigint) { // TODO this assumes the curve has a == 0 let y1Times2 = new Sum(y1).add(y1); let x1x1Times3 = new Sum(x1x1).add(x1x1).add(x1x1); - let qBound1 = assertRank1(y1Times2, m, x1x1Times3, f); + assertRank1(y1Times2, m, x1x1Times3, f); // m^2 = 2*x1 + x3 let xSum = new Sum(x1).add(x1).add(x3); - let qBound2 = assertRank1(m, m, xSum, f); + assertRank1(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 let deltaX1X3 = new Sum(x1).sub(x3); let ySum = new Sum(y1).add(y3); - let qBound3 = assertRank1(deltaX1X3, m, ySum, f); + assertRank1(deltaX1X3, m, ySum, f); // bounds checks - multiRangeCheck([mBound, x3Bound, qBound1]); - multiRangeCheck([qBound2, qBound3, Field.from(0n)]); + // TODO: there is a secret free spot for two bounds in ForeignField.mul; use it + multiRangeCheck([mBound, x3Bound, Field.from(0n)]); return { x: x3, y: y3 }; } @@ -390,18 +389,18 @@ function slice( [x0, x1, x2]: Field3, { maxBits, chunkSize }: { maxBits: number; chunkSize: number } ) { - let l = Number(L); - assert(maxBits <= 3 * l, `expected max bits <= 3*${l}, got ${maxBits}`); + let l_ = Number(l); + assert(maxBits <= 3 * l_, `expected max bits <= 3*${l_}, got ${maxBits}`); // first limb - let result0 = sliceField(x0, Math.min(l, maxBits), chunkSize); + let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); if (maxBits <= l) return result0.chunks; - maxBits -= l; + maxBits -= l_; // second limb - let result1 = sliceField(x1, Math.min(l, maxBits), chunkSize, result0); - if (maxBits <= l) return result0.chunks.concat(result1.chunks); - maxBits -= l; + let result1 = sliceField(x1, Math.min(l_, maxBits), chunkSize, result0); + if (maxBits <= l_) return result0.chunks.concat(result1.chunks); + maxBits -= l_; // third limb let result2 = sliceField(x2, maxBits, chunkSize, result1); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 9f6f096e98..c8892a725f 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -435,13 +435,11 @@ function assertRank1( // x is chained into the ffmul gate let x0 = x.finishForChaining(f); - let q2Bound = assertMul(x0, y0, xy0, f); + assertMul(x0, y0, xy0, f); // we need an extra range check on x and y, but not xy x.rangeCheck(); y.rangeCheck(); - - return q2Bound; } class Sum { From 91fef7697d668854551f9ade4731ddb2a3c8e246 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 10:57:40 +0100 Subject: [PATCH 0693/1215] remove remainder bounds check in mul() gadget --- src/lib/gadgets/foreign-field.ts | 6 ------ src/lib/gadgets/gadgets.ts | 9 ++++++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 177b16c5cd..ca99fcc2be 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -122,12 +122,6 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { // limb range checks on quotient and remainder multiRangeCheck(q); let r = compactMultiRangeCheck(r01, r2); - - // range check on r bound - // TODO: this uses two RCs too many.. need global RC stack, or get rid of bounds checks - let r2Bound = weakBound(r2, f); - multiRangeCheck([r2Bound, Field.from(0n), Field.from(0n)]); - return r; } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 50ac237eac..dc45693690 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -413,12 +413,15 @@ const Gadgets = { * * The modulus `f` does not need to be prime, but has to be smaller than 2^259. * - * **Assumptions**: In addition to the assumption that inputs are in the range [0, 2^88), as in all foreign field gadgets, + * **Assumptions**: In addition to the assumption that input limbs are in the range [0, 2^88), as in all foreign field gadgets, * this assumes an additional bound on the inputs: `x * y < 2^264 * p`, where p is the native modulus. * We usually assert this bound by proving that `x[2] < f[2] + 1`, where `x[2]` is the most significant limb of x. * To do this, use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. * The implication is that x and y are _almost_ reduced modulo f. * + * **Warning**: This gadget does not add the extra bound check on the result. + * So, to use the result in another foreign field multiplication, you have to add the bound check on it yourself, again. + * * @example * ```ts * // example modulus: secp256k1 prime @@ -450,6 +453,8 @@ const Gadgets = { * Foreign field inverse: `x^(-1) mod f` * * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * + * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. */ inv(x: Field3, f: bigint) { return ForeignField.inv(x, f); @@ -459,6 +464,8 @@ const Gadgets = { * Foreign field division: `x * y^(-1) mod f` * * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * + * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. */ div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); From 9b29f407559f1f166cbcafd6d863407e259ae82a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 11:24:57 +0100 Subject: [PATCH 0694/1215] bound y (needed for doubling) --- src/lib/gadgets/elliptic-curve.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 89494d47e2..d38cfd25c0 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -82,7 +82,7 @@ function add(p1: Point, p2: Point, f: bigint) { multiRangeCheck(y3); let mBound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); - // we dont need to bound y3[2] because it's never one of the inputs to a multiplication + let y3Bound = weakBound(y3[2], f); // (x1 - x2)*m = y1 - y2 let deltaX = new Sum(x1).sub(x2); @@ -99,7 +99,7 @@ function add(p1: Point, p2: Point, f: bigint) { assertRank1(deltaX1X3, m, ySum, f); // bounds checks - multiRangeCheck([mBound, x3Bound, Field.from(0n)]); + multiRangeCheck([mBound, x3Bound, y3Bound]); return { x: x3, y: y3 }; } @@ -135,7 +135,7 @@ function double(p1: Point, f: bigint) { multiRangeCheck(y3); let mBound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); - // we dont need to bound y3[2] because it's never one of the inputs to a multiplication + let y3Bound = weakBound(y3[2], f); // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); @@ -156,8 +156,7 @@ function double(p1: Point, f: bigint) { assertRank1(deltaX1X3, m, ySum, f); // bounds checks - // TODO: there is a secret free spot for two bounds in ForeignField.mul; use it - multiRangeCheck([mBound, x3Bound, Field.from(0n)]); + multiRangeCheck([mBound, x3Bound, y3Bound]); return { x: x3, y: y3 }; } From 38b9b7fbb4e0faafe42d55ec1cd2ecceccd65c93 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 11:49:30 +0100 Subject: [PATCH 0695/1215] tweak initial aggregator --- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/elliptic-curve.ts | 12 +++++++++--- src/lib/gadgets/elliptic-curve.unit-test.ts | 19 +++++++++++++------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 429d4c1248..4c2bfead1a 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -24,7 +24,7 @@ let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn ); -const ia = EllipticCurve.initialAggregator(BaseField, Secp256k1); +const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); const tableConfig = { windowSizeG: 3, windowSizeP: 3 }; let program = ZkProgram({ diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index d38cfd25c0..5a00485c9b 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -18,6 +18,7 @@ import { l, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; import { bigIntToBits, + bigIntToBytes, bytesToBigInt, } from '../../bindings/crypto/bigint-helpers.js'; import { @@ -355,9 +356,14 @@ function getPointTable( * It's important that this point has no known discrete logarithm so that nobody * can create an invalid proof of EC scaling. */ -function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { +function initialAggregator(Curve: CurveAffine, F: FiniteField) { + // hash that identifies the curve let h = sha256.create(); - h.update('o1js:ecdsa'); + h.update('ecdsa'); + h.update(bigIntToBytes(Curve.modulus)); + h.update(bigIntToBytes(Curve.order)); + h.update(bigIntToBytes(Curve.a)); + h.update(bigIntToBytes(Curve.b)); let bytes = h.array(); // bytes represent a 256-bit number @@ -370,7 +376,7 @@ function initialAggregator(F: FiniteField, { a, b }: { a: bigint; b: bigint }) { x = F.add(x, 1n); // solve y^2 = x^3 + ax + b let x3 = F.mul(F.square(x), x); - let y2 = F.add(x3, F.mul(a, x) + b); + let y2 = F.add(x3, F.mul(Curve.a, x) + Curve.b); y = F.sqrt(y2); } return { x, y, infinity: false }; diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index b851590e14..5569f4dbc4 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -1,10 +1,17 @@ -import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { Provable } from '../provable.js'; import { Field3 } from './foreign-field.js'; import { EllipticCurve } from './elliptic-curve.js'; import { printGates } from '../testing/constraint-system.js'; import { assert } from './common.js'; -import { Pallas } from '../../bindings/crypto/elliptic_curve.js'; +import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; +import { Fp } from '../../bindings/crypto/finite_field.js'; +import { + pallasParams, + secp256k1Params, +} from '../../bindings/crypto/elliptic-curve-examples.js'; + +const Secp256k1 = createCurveAffine(secp256k1Params); +const Pallas = createCurveAffine(pallasParams); let { add, double, initialAggregator } = EllipticCurve; @@ -17,7 +24,7 @@ let csAdd = Provable.constraintSystem(() => { let g = { x: x1, y: y1 }; let h = { x: x2, y: y2 }; - add(g, h, exampleFields.secp256k1.modulus); + add(g, h, Secp256k1.modulus); }); let csDouble = Provable.constraintSystem(() => { @@ -26,7 +33,7 @@ let csDouble = Provable.constraintSystem(() => { let g = { x: x1, y: y1 }; - double(g, exampleFields.secp256k1.modulus); + double(g, Secp256k1.modulus); }); printGates(csAdd.gates); @@ -35,6 +42,6 @@ console.log({ digest: csAdd.digest, rows: csAdd.rows }); printGates(csDouble.gates); console.log({ digest: csDouble.digest, rows: csDouble.rows }); -let point = initialAggregator(exampleFields.Fp, { a: 0n, b: 5n }); +let point = initialAggregator(Pallas, Fp); console.log({ point }); -assert(Pallas.isOnCurve(Pallas.fromAffine(point))); +assert(Pallas.isOnCurve(point)); From 69a814d898669e2c531ad8316c2f4deff553a7dc Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 11:49:34 +0100 Subject: [PATCH 0696/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 8f0e0d0f87..68df53a111 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 8f0e0d0f874dc3952bb01791c4b31394d7e5298f +Subproject commit 68df53a111e6717c594a8173f60a5c59b09aa3b2 From d0bf46d3be00ae1f97da6b8916a38fcda103d451 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:04:34 +0100 Subject: [PATCH 0697/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 5df84bf1f0..cea062267c 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5df84bf1f06c9c1e984e19d067fcf10c8ae53299 +Subproject commit cea062267c2cf81edf50fee8ca9578824c056731 From aaf14c2e5f6589054555f15f41cd67f9f4c96c8f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:05:49 +0100 Subject: [PATCH 0698/1215] 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 6658537bf1..f60540c9f8 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -185,12 +185,12 @@ "digest": "b12ad7e8a3fd28b765e059357dbe9e44" }, "leftShift": { - "rows": 7, - "digest": "66de39ad3dd5807f760341ec85a6cc41" + "rows": 5, + "digest": "451f550bf73fecf53c9be82367572cb8" }, "rightShift": { - "rows": 7, - "digest": "a32264f2d4c3092f30d600fa9506385b" + "rows": 5, + "digest": "d0793d4a326d480eaa015902dc34bc39" }, "and": { "rows": 19, From 0bf7ecaa868431e8002fd690d04caee486f6fefd Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:07:29 +0100 Subject: [PATCH 0699/1215] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a80a25b2..80b986901a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 +- Reduce number of constraints needed by `rotate()`, `leftShift()` and, `rightShift()` gadgets https://github.com/o1-labs/o1js/pull/1201 ### Fixed From be15bf118c7c8e820f1b55d531c34aa3b07dbcbd Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:15:42 +0100 Subject: [PATCH 0700/1215] remove unused function --- src/lib/gadgets/elliptic-curve.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 5a00485c9b..e7f945e570 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -536,11 +536,3 @@ const Ecdsa = { verify: verifyEcdsa, Signature: EcdsaSignature, }; - -function gcd(a: number, b: number) { - if (b > a) [a, b] = [b, a]; - while (true) { - if (b === 0) return a; - [a, b] = [b, a % b]; - } -} From ccb55c77f80ed8c2fb8563ef699cc48884e10b74 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:23:35 +0100 Subject: [PATCH 0701/1215] fixup unit test --- src/lib/gadgets/foreign-field.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index eca513d844..4cb0d2d975 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -202,8 +202,8 @@ constraintSystem.fromZkProgram( let mulChain: GateType[] = ['ForeignFieldMul', 'Zero']; let mulLayout = ifNotAllConstant( and( - contains([mulChain, mrc, mrc, mrc, mrc]), - withoutGenerics(equals([...mulChain, ...repeat(4, mrc)])) + contains([mulChain, mrc, mrc, mrc]), + withoutGenerics(equals([...mulChain, ...repeat(3, mrc)])) ) ); let invLayout = ifNotAllConstant( From 245f84722d45a731a66863cc2c531e9c8b29d79c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 12:37:31 +0100 Subject: [PATCH 0702/1215] adapt test for rot chain layout --- src/lib/gadgets/bitwise.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 61fdac25e5..73d908af18 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -228,7 +228,7 @@ constraintSystem( ifNotAllConstant(contains(xorChain(64))) ); -let rotChain: GateType[] = ['Rot64', 'RangeCheck0', 'RangeCheck0']; +let rotChain: GateType[] = ['Rot64', 'RangeCheck0']; let isJustRotate = ifNotAllConstant( and(contains(rotChain), withoutGenerics(equals(rotChain))) ); From ee766ad9df62fd3e8261b3d6615a23cf73bd02fc Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 17 Nov 2023 17:04:29 +0100 Subject: [PATCH 0703/1215] better function name --- src/lib/gadgets/foreign-field.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 00c6556876..189ffcc3f8 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -66,8 +66,8 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { let f_ = split(f); let [r0, r1, r2, overflow, carry] = exists(5, () => { - let x_ = bigint3(x); - let y_ = bigint3(y); + let x_ = toBigint3(x); + let y_ = toBigint3(y); // figure out if there's overflow let r = collapse(x_) + sign * collapse(y_); @@ -104,7 +104,7 @@ const Field3 = { * Turn a 3-tuple of Fields into a bigint */ toBigint(x: Field3): bigint { - return collapse(bigint3(x)); + return collapse(toBigint3(x)); }, /** @@ -119,7 +119,7 @@ const Field3 = { function toField3(x: bigint3): Field3 { return Tuple.map(x, (x) => new Field(x)); } -function bigint3(x: Field3): bigint3 { +function toBigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); } From c0f297d2b3747784faf1090e1db71cf5f556a3b8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 13:31:09 +0100 Subject: [PATCH 0704/1215] generalize doubleScalarMul to multiScalarMul to deduplicate logic --- src/lib/gadgets/ecdsa.unit-test.ts | 12 ++- src/lib/gadgets/elliptic-curve.ts | 124 ++++++++++++++++------------- 2 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 4c2bfead1a..cf1e12a872 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); -const tableConfig = { windowSizeG: 3, windowSizeP: 3 }; +const tableConfig = { G: { windowSize: 3 }, P: { windowSize: 3 } }; let program = ZkProgram({ name: 'ecdsa', @@ -35,14 +35,12 @@ let program = ZkProgram({ method() { let G = Point.from(Secp256k1.one); let P = Provable.witness(Point, () => publicKey); - let R = EllipticCurve.doubleScalarMul( + let R = EllipticCurve.multiScalarMul( Secp256k1, ia, - signature.s, - G, - signature.r, - P, - tableConfig + [signature.s, signature.r], + [G, P], + [tableConfig.G, tableConfig.P] ); Provable.asProver(() => { console.log(Point.toBigint(R)); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index e7f945e570..76f44813ce 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -35,7 +35,7 @@ export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; const EllipticCurve = { add, double, - doubleScalarMul, + multiScalarMul, initialAggregator, }; @@ -169,10 +169,8 @@ function verifyEcdsa( msgHash: Field3, publicKey: Point, tables?: { - windowSizeG?: number; - multiplesG?: Point[]; - windowSizeP?: number; - multiplesP?: Point[]; + G?: { windowSize: number; multiples?: Point[] }; + P?: { windowSize: number; multiples?: Point[] }; } ) { // constant case @@ -199,7 +197,13 @@ function verifyEcdsa( let u2 = ForeignField.mul(r, sInv, Curve.order); let G = Point.from(Curve.one); - let R = doubleScalarMul(Curve, ia, u1, G, u2, publicKey, tables); + let R = multiScalarMul( + Curve, + ia, + [u1, u2], + [G, publicKey], + tables && [tables.G, tables.P] + ); // this ^ already proves that R != 0 // reduce R.x modulo the curve order @@ -208,87 +212,95 @@ function verifyEcdsa( } /** - * Scalar mul that we need for ECDSA: + * Multi-scalar multiplication: * - * s*G + t*P, + * s_0 * P_0 + ... + s_(n-1) * P_(n-1) * - * where G, P are any points. The result is not allowed to be zero. + * where P_i are any points. The result is not allowed to be zero. * - * We double both points together and leverage a precomputed table - * of size 2^c to avoid all but every cth addition for both s*G and t*P. + * We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. + * + * Note: this algorithm targets a small number of points, like 2 needed for ECDSA verification. * * TODO: could use lookups for picking precomputed multiples, instead of O(2^c) provable switch * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case * TODO: glv trick which cuts down ec doubles by half by splitting s*P = s0*P + s1*endo(P) with s0, s1 in [0, 2^128) */ -function doubleScalarMul( +function multiScalarMul( Curve: CurveAffine, ia: point, - s: Field3, - G: Point, - t: Field3, - P: Point, - { - // what we called c before - windowSizeG = 1, - // G, ..., (2^c-1)*G - multiplesG = undefined as Point[] | undefined, - windowSizeP = 1, - multiplesP = undefined as Point[] | undefined, - } = {} + scalars: Field3[], + points: Point[], + tableConfigs: ( + | { + // what we called c before + windowSize?: number; + // G, ..., (2^c-1)*G + multiples?: Point[]; + } + | undefined + )[] = [] ): Point { + let n = points.length; + assert(scalars.length === n, 'Points and scalars lengths must match'); + assertPositiveInteger(n, 'Expected at least 1 point and scalar'); + // constant case if ( - Field3.isConstant(s) && - Field3.isConstant(t) && - Provable.isConstant(Point, G) && - Provable.isConstant(Point, P) + scalars.every(Field3.isConstant) && + points.every((P) => Provable.isConstant(Point, P)) ) { - let s_ = Field3.toBigint(s); - let t_ = Field3.toBigint(t); - let G_ = Point.toBigint(G); - let P_ = Point.toBigint(P); - let R = Curve.add(Curve.scale(G_, s_), Curve.scale(P_, t_)); - return Point.from(R); + // TODO dedicated MSM + let s = scalars.map(Field3.toBigint); + let P = points.map(Point.toBigint); + let sum = Curve.zero; + for (let i = 0; i < n; i++) { + sum = Curve.add(sum, Curve.scale(P[i], s[i])); + } + return Point.from(sum); } // parse or build point tables - let Gs = getPointTable(Curve, G, windowSizeG, multiplesG); - let Ps = getPointTable(Curve, P, windowSizeP, multiplesP); + let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); + let tables = points.map((P, i) => + getPointTable(Curve, P, windowSizes[i], tableConfigs[i]?.multiples) + ); // slice scalars let b = Curve.order.toString(2).length; - let ss = slice(s, { maxBits: b, chunkSize: windowSizeG }); - let ts = slice(t, { maxBits: b, chunkSize: windowSizeP }); + let scalarChunks = scalars.map((s, i) => + slice(s, { maxBits: b, chunkSize: windowSizes[i] }) + ); let sum = Point.from(ia); for (let i = b - 1; i >= 0; i--) { - if (i % windowSizeG === 0) { - // pick point to add based on the scalar chunk - let sj = ss[i / windowSizeG]; - let Gj = windowSizeG === 1 ? G : arrayGet(Point, Gs, sj, { offset: 1 }); - - // ec addition - let added = add(sum, Gj, Curve.modulus); - - // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) - sum = Provable.if(sj.equals(0), Point, sum, added); + // add in multiple of each point + for (let j = 0; j < n; j++) { + let windowSize = windowSizes[j]; + if (i % windowSize === 0) { + // pick point to add based on the scalar chunk + let sj = scalarChunks[j][i / windowSize]; + let sjP = + windowSize === 1 + ? points[j] + : arrayGet(Point, tables[j], sj, { offset: 1 }); + + // ec addition + let added = add(sum, sjP, Curve.modulus); + + // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) + sum = Provable.if(sj.equals(0), Point, sum, added); + } } - if (i % windowSizeP === 0) { - let tj = ts[i / windowSizeP]; - let Pj = windowSizeP === 1 ? P : arrayGet(Point, Ps, tj, { offset: 1 }); - let added = add(sum, Pj, Curve.modulus); - sum = Provable.if(tj.equals(0), Point, sum, added); - } if (i === 0) break; - // jointly double both points + // jointly double all points sum = double(sum, Curve.modulus); } - // the sum is now s*G + t*P + 2^(b-1)*IA + // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); Provable.equal(Point, sum, Point.from(iaFinal)).assertFalse(); From e24124b14ddbf58e7b84440849b77e808bf2bc62 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 20 Nov 2023 15:39:32 +0200 Subject: [PATCH 0705/1215] Lightnet namespace API updates. --- CHANGELOG.md | 3 +- src/lib/fetch.ts | 86 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcfed40645..e0c3dc67a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed +- `Lightnet` namespace API updates https://github.com/o1-labs/o1js/pull/XXX - Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 - Reduce number of constraints needed by `rotate()`, `leftShift()` and, `rightShift()` gadgets https://github.com/o1-labs/o1js/pull/1201 @@ -56,7 +57,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167 +- `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network) https://github.com/o1-labs/o1js/pull/1167 - Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176 - `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181 - `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182 diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 768410c1b5..2f47ad0fe9 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1019,13 +1019,15 @@ namespace Lightnet { * If an error is returned by the specified endpoint, an error is thrown. Otherwise, * the data is returned. * - * @param options.isRegularAccount Whether to acquire regular or zkApp account (one with already configured verification key) + * @param options.isRegularAccount Whether to acquire key pair of regular or zkApp account (one with already configured verification key) + * @param options.unlockAccount Whether to unlock the account by its public key after acquiring the key pair * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Key pair */ export async function acquireKeyPair( options: { isRegularAccount?: boolean; + unlockAccount?: boolean; lightnetAccountManagerEndpoint?: string; } = {} ): Promise<{ @@ -1034,10 +1036,11 @@ namespace Lightnet { }> { const { isRegularAccount = true, + unlockAccount = false, lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, } = options; const response = await fetch( - `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, + `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}&unlockAccount=${unlockAccount}`, { method: 'GET', headers: { @@ -1096,6 +1099,85 @@ namespace Lightnet { return null; } + + /** + * Gets previously acquired key pairs list. + * + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Key pairs list or null if the request failed + */ + export async function listAcquiredKeyPairs(options: { + lightnetAccountManagerEndpoint?: string; + }): Promise | null> { + const { + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/list-acquired-accounts`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return data.map((account: any) => ({ + publicKey: PublicKey.fromBase58(account.pk), + privateKey: PrivateKey.fromBase58(account.sk), + })); + } + } + + return null; + } + + /** + * Changes account lock status by its public key. + * + * @param options.isLocked Whether to lock or unlock the account + * @param options.publicKey Public key of previously acquired key pair to change the the account lock status for + * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from + * @returns Response message from the account manager as string or null if the request failed + */ + export async function changeAccountLockStatus(options: { + isLocked: boolean; + publicKey: string; + lightnetAccountManagerEndpoint?: string; + }): Promise { + const { + publicKey, + isLocked, + lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, + } = options; + const response = await fetch( + `${lightnetAccountManagerEndpoint}/${isLocked ? '' : 'un'}lock-account`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pk: publicKey, + }), + } + ); + + if (response.ok) { + const data = await response.json(); + if (data) { + return data.message as string; + } + } + + return null; + } } function updateActionState(actions: string[][], actionState: Field) { From 1bc621ea12c86d2c58ddcb6bfb1ab77cd3fb9d11 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 20 Nov 2023 15:43:30 +0200 Subject: [PATCH 0706/1215] CHANGELOG updates. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c3dc67a3..d01c6fde96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- `Lightnet` namespace API updates https://github.com/o1-labs/o1js/pull/XXX +- `Lightnet` namespace API updates https://github.com/o1-labs/o1js/pull/1256 - Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 - Reduce number of constraints needed by `rotate()`, `leftShift()` and, `rightShift()` gadgets https://github.com/o1-labs/o1js/pull/1201 From 241aae193d5b207753b0c9e60334c3ceecb5b651 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 14:52:48 +0100 Subject: [PATCH 0707/1215] save >3k constraints by optimizing array get gadget --- src/lib/gadgets/basic.ts | 68 +++++++++++++++++++++++++++++++ src/lib/gadgets/elliptic-curve.ts | 47 ++++++++++++--------- 2 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 src/lib/gadgets/basic.ts diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts new file mode 100644 index 0000000000..79209efef3 --- /dev/null +++ b/src/lib/gadgets/basic.ts @@ -0,0 +1,68 @@ +import { Fp } from '../../bindings/crypto/finite_field.js'; +import type { Field } from '../field.js'; +import { existsOne, toVar } from './common.js'; +import { Gates } from '../gates.js'; + +export { arrayGet }; + +// TODO: create constant versions of these and expose on Gadgets + +/** + * Get value from array in O(n) rows. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + * + * Note: This saves 0.5*n constraints compared to equals() + switch() + */ +function arrayGet(array: Field[], index: Field) { + index = toVar(index); + + // witness result + let a = existsOne(() => array[Number(index.toBigInt())].toBigInt()); + + // we prove a === array[j] + zj*(index - j) for some zj, for all j. + // setting j = index, this implies a === array[index] + // thanks to our assumption that the index is within bounds, we know that j = index for some j + let n = array.length; + for (let j = 0; j < n; j++) { + let zj = existsOne(() => { + let zj = Fp.div( + Fp.sub(a.toBigInt(), array[j].toBigInt()), + Fp.sub(index.toBigInt(), Fp.fromNumber(j)) + ); + return zj ?? 0n; + }); + // prove that zj*(index - j) === a - array[j] + // TODO abstract this logic into a general-purpose assertMul() gadget, + // which is able to use the constant coefficient + // (snarky's assert_r1cs somehow leads to much more constraints than this) + if (array[j].isConstant()) { + // -j*zj + zj*index - a + array[j] === 0 + Gates.generic( + { + left: -BigInt(j), + right: 0n, + out: -1n, + mul: 1n, + const: array[j].toBigInt(), + }, + { left: zj, right: index, out: a } + ); + } else { + let aMinusAj = toVar(a.sub(array[j])); + // -j*zj + zj*index - (a - array[j]) === 0 + Gates.generic( + { + left: -BigInt(j), + right: 0n, + out: -1n, + mul: 1n, + const: 0n, + }, + { left: zj, right: index, out: aMinusAj } + ); + } + } + + return a; +} diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 76f44813ce..414d39490d 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -29,6 +29,8 @@ import { import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; +import { ProvablePure } from '../../snarky.js'; +import { arrayGet } from './basic.js'; export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; @@ -262,9 +264,17 @@ function multiScalarMul( // parse or build point tables let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); - let tables = points.map((P, i) => - getPointTable(Curve, P, windowSizes[i], tableConfigs[i]?.multiples) - ); + let tables = points.map((P, i) => { + let table = getPointTable( + Curve, + P, + windowSizes[i], + tableConfigs[i]?.multiples + ); + // add zero point in the beginning + table.unshift(Point.from(Curve.zero)); + return table; + }); // slice scalars let b = Curve.order.toString(2).length; @@ -282,9 +292,7 @@ function multiScalarMul( // pick point to add based on the scalar chunk let sj = scalarChunks[j][i / windowSize]; let sjP = - windowSize === 1 - ? points[j] - : arrayGet(Point, tables[j], sj, { offset: 1 }); + windowSize === 1 ? points[j] : arrayGetGeneric(Point, tables[j], sj); // ec addition let added = add(sum, sjP, Curve.modulus); @@ -491,21 +499,22 @@ function sliceField( /** * Get value from array in O(n) constraints. * - * If the index is out of bounds, returns all-zeros version of T + * Assumes that index is in [0, n), returns an unconstrained result otherwise. */ -function arrayGet( - type: Provable, - array: T[], - index: Field, - { offset = 0 } = {} -) { - let n = array.length; - let oneHot = Array(n); - // TODO can we share computation between all those equals()? - for (let i = 0; i < n; i++) { - oneHot[i] = index.equals(i + offset); +function arrayGetGeneric(type: Provable, array: T[], index: Field) { + // witness result + let a = Provable.witness(type, () => array[Number(index)]); + let aFields = type.toFields(a); + + // constrain each field of the result + let size = type.sizeInFields(); + let arrays = array.map(type.toFields); + + for (let j = 0; j < size; j++) { + let arrayFieldsJ = arrays.map((x) => x[j]); + arrayGet(arrayFieldsJ, index).assertEquals(aFields[j]); } - return Provable.switch(oneHot, type, array); + return a; } const Point = { From f4801d0f97ae08c32eebe268d017727a4308c787 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 14:57:27 +0100 Subject: [PATCH 0708/1215] clean up table logic --- src/lib/gadgets/elliptic-curve.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 414d39490d..4e4fb65fae 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -264,17 +264,9 @@ function multiScalarMul( // parse or build point tables let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); - let tables = points.map((P, i) => { - let table = getPointTable( - Curve, - P, - windowSizes[i], - tableConfigs[i]?.multiples - ); - // add zero point in the beginning - table.unshift(Point.from(Curve.zero)); - return table; - }); + let tables = points.map((P, i) => + getPointTable(Curve, P, windowSizes[i], tableConfigs[i]?.multiples) + ); // slice scalars let b = Curve.order.toString(2).length; @@ -351,17 +343,17 @@ function getPointTable( table?: Point[] ): Point[] { assertPositiveInteger(windowSize, 'invalid window size'); - let n = (1 << windowSize) - 1; // n >= 1 + let n = 1 << windowSize; // n >= 2 assert(table === undefined || table.length === n, 'invalid table'); if (table !== undefined) return table; - table = [P]; - if (n === 1) return table; + table = [Point.from(Curve.zero), P]; + if (n === 2) return table; let Pi = double(P, Curve.modulus); table.push(Pi); - for (let i = 2; i < n; i++) { + for (let i = 3; i < n; i++) { Pi = add(Pi, P, Curve.modulus); table.push(Pi); } From 80dbef6cd64f32cc50f0305b2f7828613cbd4f09 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 15:18:41 +0100 Subject: [PATCH 0709/1215] optimal window size changed --- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/elliptic-curve.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index cf1e12a872..ff303b3cc8 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); -const tableConfig = { G: { windowSize: 3 }, P: { windowSize: 3 } }; +const tableConfig = { G: { windowSize: 4 }, P: { windowSize: 4 } }; let program = ZkProgram({ name: 'ecdsa', diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 4e4fb65fae..9127cd7898 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -297,6 +297,7 @@ function multiScalarMul( if (i === 0) break; // jointly double all points + // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) sum = double(sum, Curve.modulus); } From 1f44c58866341d8e6b32ffbcb62fca3a96cb945f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 17:48:53 +0100 Subject: [PATCH 0710/1215] implement glv --- src/lib/gadgets/basic.ts | 10 +- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/elliptic-curve.ts | 210 +++++++++++++++++++++++++---- 3 files changed, 196 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index 79209efef3..95ecf2a06b 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -2,8 +2,16 @@ import { Fp } from '../../bindings/crypto/finite_field.js'; import type { Field } from '../field.js'; import { existsOne, toVar } from './common.js'; import { Gates } from '../gates.js'; +import { Snarky } from '../../snarky.js'; -export { arrayGet }; +export { assertBoolean, arrayGet }; + +/** + * Assert that x is either 0 or 1. + */ +function assertBoolean(x: Field) { + Snarky.field.assertBoolean(x.value); +} // TODO: create constant versions of these and expose on Gadgets diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index ff303b3cc8..cf1e12a872 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); -const tableConfig = { G: { windowSize: 4 }, P: { windowSize: 4 } }; +const tableConfig = { G: { windowSize: 3 }, P: { windowSize: 3 } }; let program = ZkProgram({ name: 'ecdsa', diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 9127cd7898..89c033d0be 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -29,8 +29,7 @@ import { import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; -import { ProvablePure } from '../../snarky.js'; -import { arrayGet } from './basic.js'; +import { arrayGet, assertBoolean } from './basic.js'; export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; @@ -199,7 +198,7 @@ function verifyEcdsa( let u2 = ForeignField.mul(r, sInv, Curve.order); let G = Point.from(Curve.one); - let R = multiScalarMul( + let R = multiScalarMulGlv( Curve, ia, [u1, u2], @@ -213,6 +212,33 @@ function verifyEcdsa( Provable.assertEqual(Field3.provable, Rx, r); } +/** + * Bigint implementation of ECDSA verify + */ +function verifyEcdsaConstant( + Curve: CurveAffine, + { r, s }: { r: bigint; s: bigint }, + msgHash: bigint, + publicKey: { x: bigint; y: bigint } +) { + let q = Curve.order; + let QA = Curve.fromNonzero(publicKey); + if (!Curve.isOnCurve(QA)) return false; + if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; + if (r < 1n || r >= Curve.order) return false; + if (s < 1n || s >= Curve.order) return false; + + let sInv = inverse(s, q); + if (sInv === undefined) throw Error('impossible'); + let u1 = mod(msgHash * sInv, q); + let u2 = mod(r * sInv, q); + + let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); + if (Curve.equal(X, Curve.zero)) return false; + + return mod(X.x, q) === r; +} + /** * Multi-scalar multiplication: * @@ -310,31 +336,167 @@ function multiScalarMul( return sum; } -/** - * Bigint implementation of ECDSA verify - */ -function verifyEcdsaConstant( +function multiScalarMulGlv( Curve: CurveAffine, - { r, s }: { r: bigint; s: bigint }, - msgHash: bigint, - publicKey: { x: bigint; y: bigint } -) { - let q = Curve.order; - let QA = Curve.fromNonzero(publicKey); - if (!Curve.isOnCurve(QA)) return false; - if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; - if (r < 1n || r >= Curve.order) return false; - if (s < 1n || s >= Curve.order) return false; + ia: point, + scalars: Field3[], + points: Point[], + tableConfigs: ( + | { + // what we called c before + windowSize?: number; + // G, ..., (2^c-1)*G + multiples?: Point[]; + } + | undefined + )[] = [] +): Point { + let n = points.length; + assert(scalars.length === n, 'Points and scalars lengths must match'); + assertPositiveInteger(n, 'Expected at least 1 point and scalar'); - let sInv = inverse(s, q); - if (sInv === undefined) throw Error('impossible'); - let u1 = mod(msgHash * sInv, q); - let u2 = mod(r * sInv, q); + // constant case + if ( + scalars.every(Field3.isConstant) && + points.every((P) => Provable.isConstant(Point, P)) + ) { + // TODO dedicated MSM + let s = scalars.map(Field3.toBigint); + let P = points.map(Point.toBigint); + let sum = Curve.zero; + for (let i = 0; i < n; i++) { + sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); + } + return Point.from(sum); + } - let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); - if (Curve.equal(X, Curve.zero)) return false; + let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); + let maxBits = Curve.Endo.decomposeMaxBits; - return mod(X.x, q) === r; + // decompose scalars and handle signs + let n2 = 2 * n; + let scalars2: Field3[] = Array(n2); + let points2: Point[] = Array(n2); + let windowSizes2: number[] = Array(n2); + + for (let i = 0; i < n; i++) { + let [s0, s1] = decomposeNoRangeCheck(Curve, scalars[i]); + scalars2[2 * i] = s0.abs; + scalars2[2 * i + 1] = s1.abs; + + let endoP = endomorphism(Curve, points[i]); + points2[2 * i] = negateIf(s0.isNegative, points[i], Curve.modulus); + points2[2 * i + 1] = negateIf(s1.isNegative, endoP, Curve.modulus); + + windowSizes2[2 * i] = windowSizes2[2 * i + 1] = windowSizes[i]; + } + // from now on everything is the same as if these were the original points and scalars + points = points2; + scalars = scalars2; + windowSizes = windowSizes2; + n = n2; + + // parse or build point tables + let tables = points.map((P, i) => + getPointTable(Curve, P, windowSizes[i], undefined) + ); + + // slice scalars + let scalarChunks = scalars.map((s, i) => + slice(s, { maxBits, chunkSize: windowSizes[i] }) + ); + + let sum = Point.from(ia); + + for (let i = maxBits - 1; i >= 0; i--) { + // add in multiple of each point + for (let j = 0; j < n; j++) { + let windowSize = windowSizes[j]; + if (i % windowSize === 0) { + // pick point to add based on the scalar chunk + let sj = scalarChunks[j][i / windowSize]; + let sjP = + windowSize === 1 ? points[j] : arrayGetGeneric(Point, tables[j], sj); + + // ec addition + let added = add(sum, sjP, Curve.modulus); + + // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) + sum = Provable.if(sj.equals(0), Point, sum, added); + } + } + + if (i === 0) break; + + // jointly double all points + // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) + sum = double(sum, Curve.modulus); + } + + // the sum is now 2^(b-1)*IA + sum_i s_i*P_i + // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result + let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(maxBits - 1)); + Provable.equal(Point, sum, Point.from(iaFinal)).assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + + return sum; +} + +function negateIf(condition: Field, P: Point, f: bigint) { + let y = Provable.if( + Bool.Unsafe.ofField(condition), + Field3.provable, + P.y, + // TODO: do we need an extra bounds check here? + ForeignField.sub(Field3.from(0n), P.y, f) + ); + return { x: P.x, y }; +} + +function endomorphism(Curve: CurveAffine, P: Point) { + let beta = Field3.from(Curve.Endo.base); + let betaX = ForeignField.mul(beta, P.x, Curve.modulus); + // TODO: do we need an extra bounds check here? + return { x: betaX, y: P.y }; +} + +function decomposeNoRangeCheck(Curve: CurveAffine, s: Field3) { + // witness s0, s1 + let witnesses = exists(8, () => { + let [s0, s1] = Curve.Endo.decompose(Field3.toBigint(s)); + return [ + s0.isNegative ? 1n : 0n, + ...split(s0.abs), + s1.isNegative ? 1n : 0n, + ...split(s1.abs), + ]; + }); + let [s0Negative, s00, s01, s02, s1Negative, s10, s11, s12] = witnesses; + let s0: Field3 = [s00, s01, s02]; + let s1: Field3 = [s10, s11, s12]; + assertBoolean(s0Negative); + assertBoolean(s1Negative); + // NOTE: we do NOT range check s0, s1, because they will be split into chunks which also acts as a range check + + // prove that s1*lambda = s - s0 + let lambda = Provable.if( + Bool.Unsafe.ofField(s1Negative), + Field3.provable, + Field3.from(Curve.Scalar.negate(Curve.Endo.scalar)), + Field3.from(Curve.Endo.scalar) + ); + let rhs = Provable.if( + Bool.Unsafe.ofField(s0Negative), + Field3.provable, + new Sum(s).add(s0).finish(Curve.order), + new Sum(s).sub(s0).finish(Curve.order) + ); + assertRank1(s1, lambda, rhs, Curve.order); + + return [ + { isNegative: s0Negative, abs: s0 }, + { isNegative: s1Negative, abs: s1 }, + ] as const; } function getPointTable( From 9fceedd0d55c8deafb96c381890edc8449823b70 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 20 Nov 2023 21:12:22 +0200 Subject: [PATCH 0711/1215] Remove account locking status change capabilities. --- src/lib/fetch.ts | 46 +--------------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 2f47ad0fe9..6872b1156e 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1020,14 +1020,12 @@ namespace Lightnet { * the data is returned. * * @param options.isRegularAccount Whether to acquire key pair of regular or zkApp account (one with already configured verification key) - * @param options.unlockAccount Whether to unlock the account by its public key after acquiring the key pair * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Key pair */ export async function acquireKeyPair( options: { isRegularAccount?: boolean; - unlockAccount?: boolean; lightnetAccountManagerEndpoint?: string; } = {} ): Promise<{ @@ -1036,11 +1034,10 @@ namespace Lightnet { }> { const { isRegularAccount = true, - unlockAccount = false, lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, } = options; const response = await fetch( - `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}&unlockAccount=${unlockAccount}`, + `${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, { method: 'GET', headers: { @@ -1137,47 +1134,6 @@ namespace Lightnet { return null; } - - /** - * Changes account lock status by its public key. - * - * @param options.isLocked Whether to lock or unlock the account - * @param options.publicKey Public key of previously acquired key pair to change the the account lock status for - * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from - * @returns Response message from the account manager as string or null if the request failed - */ - export async function changeAccountLockStatus(options: { - isLocked: boolean; - publicKey: string; - lightnetAccountManagerEndpoint?: string; - }): Promise { - const { - publicKey, - isLocked, - lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, - } = options; - const response = await fetch( - `${lightnetAccountManagerEndpoint}/${isLocked ? '' : 'un'}lock-account`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - pk: publicKey, - }), - } - ); - - if (response.ok) { - const data = await response.json(); - if (data) { - return data.message as string; - } - } - - return null; - } } function updateActionState(actions: string[][], actionState: Field) { From 4c6e0806b1b9d9659281009a2738b743f628c8ef Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 20:24:05 +0100 Subject: [PATCH 0712/1215] negate gadget --- src/lib/gadgets/elliptic-curve.ts | 5 ++--- src/lib/gadgets/foreign-field.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 89c033d0be..668fba4d97 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -447,8 +447,7 @@ function negateIf(condition: Field, P: Point, f: bigint) { Bool.Unsafe.ofField(condition), Field3.provable, P.y, - // TODO: do we need an extra bounds check here? - ForeignField.sub(Field3.from(0n), P.y, f) + ForeignField.negate(P.y, f) ); return { x: P.x, y }; } @@ -456,7 +455,7 @@ function negateIf(condition: Field, P: Point, f: bigint) { function endomorphism(Curve: CurveAffine, P: Point) { let beta = Field3.from(Curve.Endo.base); let betaX = ForeignField.mul(beta, P.x, Curve.modulus); - // TODO: do we need an extra bounds check here? + // TODO: we need an extra bounds check here? return { x: betaX, y: P.y }; } diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 49dcdceb30..078f6f40e2 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -44,6 +44,7 @@ const ForeignField = { sub(x: Field3, y: Field3, f: bigint) { return sum([x, y], [-1n], f); }, + negate, sum, mul: multiply, @@ -80,6 +81,25 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { return result; } +/** + * negate() deserves a special case because we can fix the overflow to -1 + * and know that a result in range is mapped to a result in range again. + */ +function negate(x: Field3, f: bigint) { + if (Field3.isConstant(x)) { + return sum([Field3.from(0n), x], [-1n], f); + } + // provable case + x = toVars(x); + let { result, overflow } = singleAdd(Field3.from(0n), x, -1n, f); + Gates.zero(...result); + multiRangeCheck(result); + + // fix the overflow to -1 + overflow.assertEquals(-1n); + return result; +} + /** * core building block for non-native addition * From 19b8d2dd94451fcdc76408c1b0659fe976643f19 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 20:48:04 +0100 Subject: [PATCH 0713/1215] compute point multiples more cheaply --- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/elliptic-curve.ts | 51 ++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index cf1e12a872..ff303b3cc8 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); -const tableConfig = { G: { windowSize: 3 }, P: { windowSize: 3 } }; +const tableConfig = { G: { windowSize: 4 }, P: { windowSize: 4 } }; let program = ZkProgram({ name: 'ecdsa', diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 668fba4d97..6c3ab54176 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -370,7 +370,12 @@ function multiScalarMulGlv( return Point.from(sum); } + // parse or build point tables let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); + let tables = points.map((P, i) => + getPointTable(Curve, P, windowSizes[i], undefined) + ); + let maxBits = Curve.Endo.decomposeMaxBits; // decompose scalars and handle signs @@ -378,29 +383,40 @@ function multiScalarMulGlv( let scalars2: Field3[] = Array(n2); let points2: Point[] = Array(n2); let windowSizes2: number[] = Array(n2); + let tables2: Point[][] = Array(n2); + let mrcStack: Field[] = []; for (let i = 0; i < n; i++) { let [s0, s1] = decomposeNoRangeCheck(Curve, scalars[i]); scalars2[2 * i] = s0.abs; scalars2[2 * i + 1] = s1.abs; - let endoP = endomorphism(Curve, points[i]); - points2[2 * i] = negateIf(s0.isNegative, points[i], Curve.modulus); - points2[2 * i + 1] = negateIf(s1.isNegative, endoP, Curve.modulus); + let table = tables[i]; + let endoTable = table.map((P, i) => { + if (i === 0) return P; + let [phiP, betaXBound] = endomorphism(Curve, P); + mrcStack.push(betaXBound); + return phiP; + }); + tables2[2 * i] = table.map((P) => + negateIf(s0.isNegative, P, Curve.modulus) + ); + tables2[2 * i + 1] = endoTable.map((P) => + negateIf(s1.isNegative, P, Curve.modulus) + ); + points2[2 * i] = tables2[2 * i][1]; + points2[2 * i + 1] = tables2[2 * i + 1][1]; windowSizes2[2 * i] = windowSizes2[2 * i + 1] = windowSizes[i]; } + reduceMrcStack(mrcStack); // from now on everything is the same as if these were the original points and scalars points = points2; + tables = tables2; scalars = scalars2; windowSizes = windowSizes2; n = n2; - // parse or build point tables - let tables = points.map((P, i) => - getPointTable(Curve, P, windowSizes[i], undefined) - ); - // slice scalars let scalarChunks = scalars.map((s, i) => slice(s, { maxBits, chunkSize: windowSizes[i] }) @@ -455,8 +471,7 @@ function negateIf(condition: Field, P: Point, f: bigint) { function endomorphism(Curve: CurveAffine, P: Point) { let beta = Field3.from(Curve.Endo.base); let betaX = ForeignField.mul(beta, P.x, Curve.modulus); - // TODO: we need an extra bounds check here? - return { x: betaX, y: P.y }; + return [{ x: betaX, y: P.y }, weakBound(betaX[2], Curve.modulus)] as const; } function decomposeNoRangeCheck(Curve: CurveAffine, s: Field3) { @@ -711,3 +726,19 @@ const Ecdsa = { verify: verifyEcdsa, Signature: EcdsaSignature, }; + +// MRC stack + +function reduceMrcStack(xs: Field[]) { + let n = xs.length; + let nRemaining = n % 3; + let nFull = (n - nRemaining) / 3; + for (let i = 0; i < nFull; i++) { + multiRangeCheck([xs[3 * i], xs[3 * i + 1], xs[3 * i + 2]]); + } + let remaining: Field3 = [Field.from(0n), Field.from(0n), Field.from(0n)]; + for (let i = 0; i < nRemaining; i++) { + remaining[i] = xs[3 * nFull + i]; + } + multiRangeCheck(remaining); +} From 3105fbc9f00586d4b18001b08674a303cd4dc4be Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 20:48:19 +0100 Subject: [PATCH 0714/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 68df53a111..bf1c0747bb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 68df53a111e6717c594a8173f60a5c59b09aa3b2 +Subproject commit bf1c0747bbd154098faaca17ca0b4c135f75f805 From b592ecf846014e781f22c259cb7e4c87d6331d4c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 20 Nov 2023 12:38:49 -0800 Subject: [PATCH 0715/1215] fix(fetch.ts): remove reversing actions, as they are in correct order already --- src/lib/fetch.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 8723d5ba1e..503babbec4 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -951,10 +951,8 @@ async function fetchActions( break; } } - // Archive Node API returns actions in the latest order, so we reverse the array to get the actions in chronological order. - fetchedActions.reverse(); - let actionsList: { actions: string[][]; hash: string }[] = []; + let actionsList: { actions: string[][]; hash: string }[] = []; // correct for archive node sending one block too many if ( fetchedActions.length !== 0 && From b91b0336513096cd52f9f6dfb48229459356a0a6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 20 Nov 2023 12:40:47 -0800 Subject: [PATCH 0716/1215] docs(CHANGELOG.md): update changelog with recent bug fix Add details about the bug fix where array reversal of fetched actions was removed as they are returned in the correct order. This provides users with accurate information about the changes in the latest version. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ff71d070..254e3dea9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.and()`, new provable method to support bitwise and for native field elements. https://github.com/o1-labs/o1js/pull/1193 - `Gadgets.multiRangeCheck()` and `Gadgets.compactMultiRangeCheck()`, two building blocks for non-native arithmetic with bigints of size up to 264 bits. https://github.com/o1-labs/o1js/pull/1216 +### Fixed + +- Removed array reversal of fetched actions, since they are returned in the correct order. https://github.com/o1-labs/o1js/pull/1258 + ## [0.14.0](https://github.com/o1-labs/o1js/compare/045faa7...e8e7510e1) ### Breaking changes From 73d2b2dee9228006d0ce77733d1c2a3e43a0a6a1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 22:02:02 +0100 Subject: [PATCH 0717/1215] replace mul input rcs with generic gates --- src/lib/gadgets/elliptic-curve.ts | 1 - src/lib/gadgets/foreign-field.ts | 106 ++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 9127cd7898..a2a180adb2 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -29,7 +29,6 @@ import { import { Bool } from '../bool.js'; import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; -import { ProvablePure } from '../../snarky.js'; import { arrayGet } from './basic.js'; export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 49dcdceb30..374a26a0d5 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,6 +5,7 @@ import { import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; +import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { assert, bitSlice, exists, toVars } from './common.js'; import { @@ -402,14 +403,15 @@ function split2(x: bigint): [bigint, bigint] { /** * Optimized multiplication of sums, like (x + y)*z = a + b + c * - * We use two optimizations over naive summing and then multiplying: + * We use several optimizations over naive summing and then multiplying: * * - we skip the range check on the remainder sum, because ffmul is sound with r being a sum of range-checked values + * - we replace the range check on the input sums with an extra low limb sum using generic gates * - we chain the first input's sum into the ffmul gate * * As usual, all values are assumed to be range checked, and the left and right multiplication inputs * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. - * However, all extra checks that are needed on the sums are handled here. + * However, all extra checks that are needed on the _sums_ are handled here. * * TODO example */ @@ -419,19 +421,19 @@ function assertRank1( xy: Field3 | Sum, f: bigint ) { - x = Sum.fromUnfinished(x, f); - y = Sum.fromUnfinished(y, f); - xy = Sum.fromUnfinished(xy, f); + x = Sum.fromUnfinished(x); + y = Sum.fromUnfinished(y); + xy = Sum.fromUnfinished(xy); // finish the y and xy sums with a zero gate let y0 = y.finish(f); let xy0 = xy.finish(f); // x is chained into the ffmul gate - let x0 = x.finishForChaining(f); + let x0 = x.finish(f, true); assertMul(x0, y0, xy0, f); - // we need an extra range check on x and y, but not xy + // we need and extra range check on x and y x.rangeCheck(); y.rangeCheck(); } @@ -464,10 +466,17 @@ class Sum { return this; } - finish(f: bigint, forChaining = false) { + finishOne() { + let result = this.#summands[0]; + this.#result = result; + return result; + } + + finish(f: bigint, isChained = false) { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; + if (n === 0) return this.finishOne(); let x = this.#summands.map(toVars); let result = x[0]; @@ -475,14 +484,87 @@ class Sum { for (let i = 0; i < n; i++) { ({ result } = singleAdd(result, x[i + 1], signs[i], f)); } - if (n > 0 && !forChaining) Gates.zero(...result); + if (!isChained) Gates.zero(...result); this.#result = result; return result; } - finishForChaining(f: bigint) { - return this.finish(f, true); + finishForMulInput(f: bigint, isChained = false) { + assert(this.#result === undefined, 'sum already finished'); + let signs = this.#ops; + let n = signs.length; + if (n === 0) return this.finishOne(); + + let x = this.#summands.map(toVars); + + // since the sum becomes a multiplication input, we need to constrain all limbs _individually_. + // sadly, ffadd only constrains the low and middle limb together. + // we could fix it with a RC just for the lower two limbs + // but it's cheaper to add generic gates which handle the lowest limb separately, and avoids the unfilled MRC slot + let f_ = split(f); + + // compute witnesses for generic gates -- overflows and carries + let nFields = Provable.Array(Field, n); + let [overflows, carries] = Provable.witness( + provableTuple([nFields, nFields]), + () => { + let overflows: bigint[] = []; + let carries: bigint[] = []; + + let r = Field3.toBigint(x[0]); + + for (let i = 0; i < n; i++) { + // this duplicates some of the logic in singleAdd + let x_ = split(r); + let y_ = bigint3(x[i + 1]); + let sign = signs[i]; + + // figure out if there's overflow + r = r + sign * collapse(y_); + let overflow = 0n; + if (sign === 1n && r >= f) overflow = 1n; + if (sign === -1n && r < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; + overflows.push(overflow); + + // add with carry, only on the lowest limb + let r0 = x_[0] + sign * y_[0] - overflow * f_[0]; + carries.push(r0 >> l); + } + return [overflows.map(Field.from), carries.map(Field.from)]; + } + ); + + // generic gates for low limbs + let result0 = x[0][0]; + let r0s: Field[] = []; + for (let i = 0; i < n; i++) { + // constrain carry to 0, 1, or -1 + let c = carries[i]; + c.mul(c.sub(1n)).mul(c.add(1n)).assertEquals(0n); + + result0 = result0 + .add(x[i + 1][0].mul(signs[i])) + .add(overflows[i].mul(f_[0])) + .sub(c.mul(1n << l)) + .seal(); + r0s.push(result0); + } + + // ffadd chain + let result = x[0]; + for (let i = 0; i < n; i++) { + let r = singleAdd(result, x[i + 1], signs[i], f); + // wire low limb and overflow to previous values + r.result[0].assertEquals(r0s[i]); + r.overflow.assertEquals(overflows[i]); + result = r.result; + } + if (!isChained) Gates.zero(...result); + + this.#result = result; + return result; } rangeCheck() { @@ -490,7 +572,7 @@ class Sum { if (this.#ops.length > 0) multiRangeCheck(this.#result); } - static fromUnfinished(x: Field3 | Sum, f: bigint) { + static fromUnfinished(x: Field3 | Sum) { if (x instanceof Sum) { assert(x.#result === undefined, 'sum already finished'); return x; From 2c3abba6ad665f27106b72a567becaae2797a8a3 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Tue, 21 Nov 2023 09:52:09 +0200 Subject: [PATCH 0718/1215] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01c6fde96..3dd5d76775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- `Lightnet` namespace API updates https://github.com/o1-labs/o1js/pull/1256 +- `Lightnet` namespace API updates with added `listAcquiredKeyPairs()` method https://github.com/o1-labs/o1js/pull/1256 - Expose raw provable methods of a `ZkProgram` on `zkProgram.rawMethods` https://github.com/o1-labs/o1js/pull/1241 - Reduce number of constraints needed by `rotate()`, `leftShift()` and, `rightShift()` gadgets https://github.com/o1-labs/o1js/pull/1201 From 5020ce320dc43a4fb34f1a1c460e2aeb0aad7189 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 10:00:07 +0100 Subject: [PATCH 0719/1215] simplify doc comments, avoid confusion --- src/lib/int.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 3c3391d99a..a463b24998 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -213,7 +213,7 @@ class UInt64 extends CircuitValue { * * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#xor-1) * - * @param x {@link UInt64} element to compare. + * @param x {@link UInt64} element to XOR. * * @example * ```ts @@ -255,13 +255,16 @@ class UInt64 extends CircuitValue { * let a = UInt64.from(0b0101); * let b = a.not(false); * - * b.assertEquals(0b1010); + * console.log(b.toBigInt().toString(2)); + * // 1111111111111111111111111111111111111111111111111111111111111010 * * // NOTing 4 bits with the checked version utilizing the xor gadget * let a = UInt64.from(0b0101); * let b = a.not(); * - * b.assertEquals(0b1010); + * console.log(b.toBigInt().toString(2)); + * // 1111111111111111111111111111111111111111111111111111111111111010 + * * ``` * * @param a - The value to apply NOT to. @@ -369,7 +372,7 @@ class UInt64 extends CircuitValue { * let a = UInt64.from(3); // ... 000011 * let b = UInt64.from(5); // ... 000101 * - * let c = a.and(b, 2); // ... 000001 + * let c = a.and(b); // ... 000001 * c.assertEquals(1); * ``` */ From 63fa85fd708e5182b86e5cf7e5000059fec4128a Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 10:01:44 +0100 Subject: [PATCH 0720/1215] same for uint32 --- src/lib/int.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index a463b24998..186a8d381b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -747,7 +747,7 @@ class UInt32 extends CircuitValue { * NOT `~` operator in JavaScript](https://developer.mozilla.org/en-US/docs/ * Web/JavaScript/Reference/Operators/Bitwise_NOT). * - * **Note:** The NOT gate operates over 64 bit for UInt64 types. + * **Note:** The NOT gate operates over 32 bit for UInt32 types. * * A NOT gate works by returning `1` in each bit position if the * corresponding bit of the operand is `0`, and returning `0` if the @@ -769,13 +769,16 @@ class UInt32 extends CircuitValue { * let a = UInt32.from(0b0101); * let b = a.not(false); * - * b.assertEquals(0b1010); + * console.log(b.toBigInt().toString(2)); + * // 11111111111111111111111111111010 * * // NOTing 4 bits with the checked version utilizing the xor gadget * let a = UInt32.from(0b0101); * let b = a.not(); * - * b.assertEquals(0b1010); + * console.log(b.toBigInt().toString(2)); + * // 11111111111111111111111111111010 + * * ``` * * @param a - The value to apply NOT to. From 21050b6391942cabaa5edaf4f9518fb0f6b4d040 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:03:43 +0100 Subject: [PATCH 0721/1215] enable strict typing of variable fields --- src/lib/field.ts | 15 +++++++++++++-- src/lib/gadgets/common.ts | 16 ++++++++++------ src/snarky.d.ts | 8 ++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index de52ecf621..9e2497e231 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -11,10 +11,12 @@ export { Field }; // internal API export { - ConstantField, FieldType, FieldVar, FieldConst, + ConstantField, + VarField, + VarFieldVar, isField, withMessage, readVarMessage, @@ -69,6 +71,7 @@ type FieldVar = | [FieldType.Scale, FieldConst, FieldVar]; type ConstantFieldVar = [FieldType.Constant, FieldConst]; +type VarFieldVar = [FieldType.Var, number]; const FieldVar = { constant(x: bigint | FieldConst): ConstantFieldVar { @@ -78,6 +81,9 @@ const FieldVar = { isConstant(x: FieldVar): x is ConstantFieldVar { return x[0] === FieldType.Constant; }, + isVar(x: FieldVar): x is VarFieldVar { + return x[0] === FieldType.Var; + }, add(x: FieldVar, y: FieldVar): FieldVar { if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; @@ -101,6 +107,7 @@ const FieldVar = { }; type ConstantField = Field & { value: ConstantFieldVar }; +type VarField = Field & { value: VarFieldVar }; /** * A {@link Field} is an element of a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field). @@ -1039,7 +1046,7 @@ class Field { seal() { if (this.isConstant()) return this; let x = Snarky.field.seal(this.value); - return new Field(x); + return VarField(x); } /** @@ -1360,3 +1367,7 @@ there is \`Provable.asProver(() => { ... })\` which allows you to use ${varName} Warning: whatever happens inside asProver() will not be part of the zk proof. `; } + +function VarField(x: VarFieldVar): VarField { + return new Field(x) as VarField; +} diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 196ca64e73..1b52023ab1 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,5 +1,5 @@ import { Provable } from '../provable.js'; -import { Field, FieldConst, FieldVar, FieldType } from '../field.js'; +import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; @@ -21,7 +21,7 @@ export { function existsOne(compute: () => bigint) { let varMl = Snarky.existsVar(() => FieldConst.fromBigint(compute())); - return new Field(varMl); + return VarField(varMl); } function exists TupleN>( @@ -31,7 +31,7 @@ function exists TupleN>( let varsMl = Snarky.exists(n, () => MlArray.mapTo(compute(), FieldConst.fromBigint) ); - let vars = MlArray.mapFrom(varsMl, (v) => new Field(v)); + let vars = MlArray.mapFrom(varsMl, VarField); return TupleN.fromArray(n, vars); } @@ -43,20 +43,24 @@ function exists TupleN>( * * Same as `Field.seal()` with the difference that `seal()` leaves constants as is. */ -function toVar(x: Field | bigint) { +function toVar(x: Field | bigint): VarField { // don't change existing vars - if (x instanceof Field && x.value[1] === FieldType.Var) return x; + if (isVar(x)) return x; let xVar = existsOne(() => Field.from(x).toBigInt()); xVar.assertEquals(x); return xVar; } +function isVar(x: Field | bigint): x is VarField { + return x instanceof Field && FieldVar.isVar(x.value); +} + /** * Apply {@link toVar} to each element of a tuple. */ function toVars>( fields: T -): { [k in keyof T]: Field } { +): { [k in keyof T]: VarField } { return Tuple.map(fields, toVar); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 69fe3bcb37..343d714c46 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,5 +1,5 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; -import type { Field, FieldConst, FieldVar } from './lib/field.js'; +import type { Field, FieldConst, FieldVar, VarFieldVar } from './lib/field.js'; import type { BoolVar, Bool } from './lib/bool.js'; import type { ScalarConst } from './lib/scalar.js'; import type { @@ -181,11 +181,11 @@ declare const Snarky: { exists( sizeInFields: number, compute: () => MlArray - ): MlArray; + ): MlArray; /** * witness a single field element variable */ - existsVar(compute: () => FieldConst): FieldVar; + existsVar(compute: () => FieldConst): VarFieldVar; /** * APIs that have to do with running provable code @@ -281,7 +281,7 @@ declare const Snarky: { * returns a new witness from an AST * (implemented with toConstantAndTerms) */ - seal(x: FieldVar): FieldVar; + seal(x: FieldVar): VarFieldVar; /** * Unfolds AST to get `x = c + c0*Var(i0) + ... + cn*Var(in)`, * returns `(c, [(c0, i0), ..., (cn, in)])`; From 89e0cddf05d357d9994bf6f5680c7ba163173e64 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:07:41 +0100 Subject: [PATCH 0722/1215] optimized assertOneOf gadget --- src/lib/gadgets/basic.ts | 70 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index 79209efef3..c48b84e3e8 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -1,9 +1,10 @@ import { Fp } from '../../bindings/crypto/finite_field.js'; -import type { Field } from '../field.js'; +import type { Field, VarField } from '../field.js'; import { existsOne, toVar } from './common.js'; import { Gates } from '../gates.js'; +import { TupleN } from '../util/types.js'; -export { arrayGet }; +export { arrayGet, assertOneOf }; // TODO: create constant versions of these and expose on Gadgets @@ -66,3 +67,68 @@ function arrayGet(array: Field[], index: Field) { return a; } + +/** + * Assert that a value equals one of a finite list of constants: + * `(x - c1)*(x - c2)*...*(x - cn) === 0` + * + * TODO: what prevents us from getting the same efficiency with snarky DSL code? + */ +function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { + let xv = toVar(x); + let [c1, c2, ...c] = allowed; + let n = c.length; + if (n === 0) { + // (x - c1)*(x - c2) === 0 + assertBilinearZero(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + return; + } + // z = (x - c1)*(x - c2) + let z = bilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + + for (let i = 0; i < n; i++) { + if (i < n - 1) { + // z = z*(x - c) + z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); + } else { + // z*(x - c) === 0 + assertBilinearZero(z, xv, [1n, -c[i], 0n, 0n]); + } + } +} + +// low-level helpers to create generic gates + +/** + * Compute bilinear function of x and y: + * z = a*x*y + b*x + c*y + d + */ +function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { + let z = existsOne(() => { + let x0 = x.toBigInt(); + let y0 = y.toBigInt(); + return a * x0 * y0 + b * x0 + c * y0 + d; + }); + // b*x + c*y - z + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: -1n, mul: a, const: d }, + { left: x, right: y, out: z } + ); + return z; +} + +/** + * Assert bilinear equation on x and y: + * a*x*y + b*x + c*y + d === 0 + */ +function assertBilinearZero( + x: VarField, + y: VarField, + [a, b, c, d]: TupleN +) { + // b*x + c*y + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: 0n, mul: a, const: d }, + { left: x, right: y, out: x } + ); +} From d0a315910a18cddebcf5f624b8f6ccc81f4cb767 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:29:33 +0100 Subject: [PATCH 0723/1215] simplify low-level gadgets --- src/lib/gadgets/basic.ts | 63 +++++++++++++++------------------------ src/lib/gadgets/common.ts | 1 + 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index c48b84e3e8..b7cd69e446 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -16,52 +16,34 @@ export { arrayGet, assertOneOf }; * Note: This saves 0.5*n constraints compared to equals() + switch() */ function arrayGet(array: Field[], index: Field) { - index = toVar(index); + let i = toVar(index); // witness result - let a = existsOne(() => array[Number(index.toBigInt())].toBigInt()); + let a = existsOne(() => array[Number(i.toBigInt())].toBigInt()); - // we prove a === array[j] + zj*(index - j) for some zj, for all j. - // setting j = index, this implies a === array[index] - // thanks to our assumption that the index is within bounds, we know that j = index for some j + // we prove a === array[j] + zj*(i - j) for some zj, for all j. + // setting j = i, this implies a === array[i] + // thanks to our assumption that the index i is within bounds, we know that j = i for some j let n = array.length; for (let j = 0; j < n; j++) { let zj = existsOne(() => { let zj = Fp.div( Fp.sub(a.toBigInt(), array[j].toBigInt()), - Fp.sub(index.toBigInt(), Fp.fromNumber(j)) + Fp.sub(i.toBigInt(), Fp.fromNumber(j)) ); return zj ?? 0n; }); - // prove that zj*(index - j) === a - array[j] + // prove that zj*(i - j) === a - array[j] // TODO abstract this logic into a general-purpose assertMul() gadget, // which is able to use the constant coefficient // (snarky's assert_r1cs somehow leads to much more constraints than this) if (array[j].isConstant()) { - // -j*zj + zj*index - a + array[j] === 0 - Gates.generic( - { - left: -BigInt(j), - right: 0n, - out: -1n, - mul: 1n, - const: array[j].toBigInt(), - }, - { left: zj, right: index, out: a } - ); + // zj*i + (-j)*zj + 0*i + array[j] === a + assertBilinear(zj, i, [1n, -BigInt(j), 0n, array[j].toBigInt()], a); } else { let aMinusAj = toVar(a.sub(array[j])); - // -j*zj + zj*index - (a - array[j]) === 0 - Gates.generic( - { - left: -BigInt(j), - right: 0n, - out: -1n, - mul: 1n, - const: 0n, - }, - { left: zj, right: index, out: aMinusAj } - ); + // zj*i + (-j)*zj + 0*i + 0 === (a - array[j]) + assertBilinear(zj, i, [1n, -BigInt(j), 0n, 0n], aMinusAj); } } @@ -80,7 +62,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { let n = c.length; if (n === 0) { // (x - c1)*(x - c2) === 0 - assertBilinearZero(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + assertBilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); return; } // z = (x - c1)*(x - c2) @@ -92,7 +74,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); } else { // z*(x - c) === 0 - assertBilinearZero(z, xv, [1n, -c[i], 0n, 0n]); + assertBilinear(z, xv, [1n, -c[i], 0n, 0n]); } } } @@ -101,7 +83,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { /** * Compute bilinear function of x and y: - * z = a*x*y + b*x + c*y + d + * `z = a*x*y + b*x + c*y + d` */ function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { let z = existsOne(() => { @@ -118,17 +100,20 @@ function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { } /** - * Assert bilinear equation on x and y: - * a*x*y + b*x + c*y + d === 0 + * Assert bilinear equation on x, y and z: + * `a*x*y + b*x + c*y + d === z` + * + * The default for z is 0. */ -function assertBilinearZero( +function assertBilinear( x: VarField, y: VarField, - [a, b, c, d]: TupleN + [a, b, c, d]: TupleN, + z?: VarField ) { - // b*x + c*y + a*x*y + d === 0 + // b*x + c*y - z + a*x*y + d === z Gates.generic( - { left: b, right: c, out: 0n, mul: a, const: d }, - { left: x, right: y, out: x } + { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, + { left: x, right: y, out: z === undefined ? x : z } ); } diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 1b52023ab1..e6e6c873cc 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -12,6 +12,7 @@ export { existsOne, toVars, toVar, + isVar, assert, bitSlice, witnessSlice, From 64610e7d13e5135ca74f4838adad2ad91abbd2b8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:30:33 +0100 Subject: [PATCH 0724/1215] make new assertRank1 more efficient and enable it --- src/lib/gadgets/elliptic-curve.ts | 4 +--- src/lib/gadgets/foreign-field.ts | 36 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index a2a180adb2..60d9646d5c 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -85,6 +85,7 @@ function add(p1: Point, p2: Point, f: bigint) { let mBound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); let y3Bound = weakBound(y3[2], f); + multiRangeCheck([mBound, x3Bound, y3Bound]); // (x1 - x2)*m = y1 - y2 let deltaX = new Sum(x1).sub(x2); @@ -100,9 +101,6 @@ function add(p1: Point, p2: Point, f: bigint) { let ySum = new Sum(y1).add(y3); assertRank1(deltaX1X3, m, ySum, f); - // bounds checks - multiRangeCheck([mBound, x3Bound, y3Bound]); - return { x: x3, y: y3 }; } diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 374a26a0d5..b0abeff8c1 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -7,7 +7,8 @@ import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; -import { assert, bitSlice, exists, toVars } from './common.js'; +import { assertOneOf } from './basic.js'; +import { assert, bitSlice, exists, toVar, toVars } from './common.js'; import { l, lMask, @@ -426,16 +427,12 @@ function assertRank1( xy = Sum.fromUnfinished(xy); // finish the y and xy sums with a zero gate - let y0 = y.finish(f); + let y0 = y.finishForMulInput(f); let xy0 = xy.finish(f); // x is chained into the ffmul gate - let x0 = x.finish(f, true); + let x0 = x.finishForMulInput(f, true); assertMul(x0, y0, xy0, f); - - // we need and extra range check on x and y - x.rangeCheck(); - y.rangeCheck(); } class Sum { @@ -490,6 +487,7 @@ class Sum { return result; } + // TODO this is complex and should be removed once we fix the ffadd gate to constrain all limbs individually finishForMulInput(f: bigint, isChained = false) { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; @@ -537,19 +535,21 @@ class Sum { ); // generic gates for low limbs - let result0 = x[0][0]; - let r0s: Field[] = []; + let x0 = x[0][0]; + let x0s: Field[] = []; for (let i = 0; i < n; i++) { // constrain carry to 0, 1, or -1 let c = carries[i]; - c.mul(c.sub(1n)).mul(c.add(1n)).assertEquals(0n); - - result0 = result0 - .add(x[i + 1][0].mul(signs[i])) - .add(overflows[i].mul(f_[0])) - .sub(c.mul(1n << l)) - .seal(); - r0s.push(result0); + assertOneOf(c, [0n, 1n, -1n]); + + // x0 <- x0 + s*y0 - o*f0 - c*2^l + x0 = toVar( + x0 + .add(x[i + 1][0].mul(signs[i])) + .sub(overflows[i].mul(f_[0])) + .sub(c.mul(1n << l)) + ); + x0s.push(x0); } // ffadd chain @@ -557,7 +557,7 @@ class Sum { for (let i = 0; i < n; i++) { let r = singleAdd(result, x[i + 1], signs[i], f); // wire low limb and overflow to previous values - r.result[0].assertEquals(r0s[i]); + r.result[0].assertEquals(x0s[i]); r.overflow.assertEquals(overflows[i]); result = r.result; } From 6968c257cbe494eb3f1d049956b51c9a607451e6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:51:34 +0100 Subject: [PATCH 0725/1215] tweak --- src/lib/gadgets/basic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index 82ede36b5f..91c643b6b0 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -10,7 +10,7 @@ export { assertBoolean, arrayGet, assertOneOf }; /** * Assert that x is either 0 or 1. */ -function assertBoolean(x: Field) { +function assertBoolean(x: VarField) { Snarky.field.assertBoolean(x.value); } From 3932338630ce11a02fc0f28fa873104c0afc263c Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 11:51:35 +0100 Subject: [PATCH 0726/1215] bump bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index cea062267c..2d87533018 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit cea062267c2cf81edf50fee8ca9578824c056731 +Subproject commit 2d875330187b95eb36f4602006e46ab2dcfe4cdd From acffbcea6cf050e624bf42a797100784dd3a3e64 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 12:12:27 +0100 Subject: [PATCH 0727/1215] tweak decompose gadget --- src/lib/gadgets/elliptic-curve.ts | 32 +++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index bf64cceb72..028c6a77cc 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -14,7 +14,7 @@ import { split, weakBound, } from './foreign-field.js'; -import { l, multiRangeCheck } from './range-check.js'; +import { l, l2, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; import { bigIntToBits, @@ -375,6 +375,7 @@ function multiScalarMulGlv( ); let maxBits = Curve.Endo.decomposeMaxBits; + assert(maxBits < l2, 'decomposed scalars assumed to be < 2*88 bits'); // decompose scalars and handle signs let n2 = 2 * n; @@ -472,23 +473,34 @@ function endomorphism(Curve: CurveAffine, P: Point) { return [{ x: betaX, y: P.y }, weakBound(betaX[2], Curve.modulus)] as const; } +/** + * Decompose s = s0 + s1*lambda where s0, s1 are guaranteed to be small + * + * Note: This assumes that s0 and s1 are range-checked externally; in scalar multiplication this happens because they are split into chunks. + */ function decomposeNoRangeCheck(Curve: CurveAffine, s: Field3) { + assert( + Curve.Endo.decomposeMaxBits < l2, + 'decomposed scalars assumed to be < 2*88 bits' + ); // witness s0, s1 - let witnesses = exists(8, () => { + let witnesses = exists(6, () => { let [s0, s1] = Curve.Endo.decompose(Field3.toBigint(s)); + let [s00, s01] = split(s0.abs); + let [s10, s11] = split(s1.abs); + // prettier-ignore return [ - s0.isNegative ? 1n : 0n, - ...split(s0.abs), - s1.isNegative ? 1n : 0n, - ...split(s1.abs), + s0.isNegative ? 1n : 0n, s00, s01, + s1.isNegative ? 1n : 0n, s10, s11, ]; }); - let [s0Negative, s00, s01, s02, s1Negative, s10, s11, s12] = witnesses; - let s0: Field3 = [s00, s01, s02]; - let s1: Field3 = [s10, s11, s12]; + let [s0Negative, s00, s01, s1Negative, s10, s11] = witnesses; + // we can hard-code highest limb to zero + // (in theory this would allow us to hard-code the high quotient limb to zero in the ffmul below, and save 2 RCs.. but not worth it) + let s0: Field3 = [s00, s01, Field.from(0n)]; + let s1: Field3 = [s10, s11, Field.from(0n)]; assertBoolean(s0Negative); assertBoolean(s1Negative); - // NOTE: we do NOT range check s0, s1, because they will be split into chunks which also acts as a range check // prove that s1*lambda = s - s0 let lambda = Provable.if( From 8c524b678b1cc3eccd3c4dde0b17a0d5d81c8949 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:49:25 +0100 Subject: [PATCH 0728/1215] add divMod32, addMod32 --- src/lib/gadgets/arithmetic.ts | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/lib/gadgets/arithmetic.ts diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts new file mode 100644 index 0000000000..0e70d5a580 --- /dev/null +++ b/src/lib/gadgets/arithmetic.ts @@ -0,0 +1,40 @@ +import { Field } from '../core.js'; +import { Provable } from '../provable.js'; +import { rangeCheck32 } from './range-check.js'; + +export { divMod32, addMod32 }; + +function divMod32(n: Field) { + if (n.isConstant()) { + let nBigInt = n.toBigInt(); + let q = nBigInt / (1n << 32n); + let r = nBigInt - q * (1n << 32n); + return { + remainder: new Field(r), + quotient: new Field(q), + }; + } + + let qr = Provable.witness(Provable.Array(Field, 2), () => { + let nBigInt = n.toBigInt(); + let q = nBigInt / (1n << 32n); + let r = nBigInt - q * (1n << 32n); + return [new Field(q), new Field(r)]; + }); + let [q, r] = qr; + + // I think we can "skip" this here and do it in the caller - see rotate32 + // rangeCheck32(q); + // rangeCheck32(r); + + n.assertEquals(q.mul(1n << 32n).add(r)); + + return { + remainder: r, + quotient: q, + }; +} + +function addMod32(x: Field, y: Field) { + return divMod32(x.add(y)).remainder; +} From 7d8ff8e20d5f575bffb19b1fd982676d4da2094f Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:49:36 +0100 Subject: [PATCH 0729/1215] rotate32, rename rotate to rotate64 --- src/lib/gadgets/bitwise.ts | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 7d112664e7..dc893b408b 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -10,9 +10,10 @@ import { divideWithRemainder, toVar, } from './common.js'; -import { rangeCheck64 } from './range-check.js'; +import { rangeCheck32, rangeCheck64 } from './range-check.js'; +import { divMod32 } from './arithmetic.js'; -export { xor, not, rotate, and, rightShift, leftShift }; +export { xor, not, rotate64, rotate32, and, rightShift, leftShift }; function not(a: Field, length: number, checked: boolean = false) { // check that input length is positive @@ -196,7 +197,7 @@ function and(a: Field, b: Field, length: number) { return outputAnd; } -function rotate( +function rotate64( field: Field, bits: number, direction: 'left' | 'right' = 'left' @@ -214,11 +215,29 @@ function rotate( ); return new Field(Fp.rot(field.toBigInt(), bits, direction)); } - const [rotated] = rot(field, bits, direction); + const [rotated] = rot64(field, bits, direction); return rotated; } -function rot( +function rotate32( + field: Field, + bits: number, + direction: 'left' | 'right' = 'left' +) { + assert(bits <= 32 && bits > 0, 'bits must be between 0 and 32'); + + let { quotient: excess, remainder: shifted } = divMod32( + field.mul(1n << BigInt(direction === 'left' ? bits : 32 - bits)) + ); + + let rotated = shifted.add(excess); + + rangeCheck32(rotated); + + return rotated; +} + +function rot64( field: Field, bits: number, direction: 'left' | 'right' = 'left' @@ -296,7 +315,7 @@ function rightShift(field: Field, bits: number) { ); return new Field(Fp.rightShift(field.toBigInt(), bits)); } - const [, excess] = rot(field, bits, 'right'); + const [, excess] = rot64(field, bits, 'right'); return excess; } @@ -313,6 +332,6 @@ function leftShift(field: Field, bits: number) { ); return new Field(Fp.leftShift(field.toBigInt(), bits)); } - const [, , shifted] = rot(field, bits, 'left'); + const [, , shifted] = rot64(field, bits, 'left'); return shifted; } From 27d6f8abb6eef353d7691eb63dc24bc606217abd Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:49:46 +0100 Subject: [PATCH 0730/1215] add rotate32 tests --- src/lib/gadgets/bitwise.unit-test.ts | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 559cea7e2a..980610e92c 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -57,10 +57,16 @@ let Bitwise = ZkProgram({ return Gadgets.and(a, b, 64); }, }, - rot: { + rot32: { privateInputs: [Field], method(a: Field) { - return Gadgets.rotate(a, 12, 'left'); + return Gadgets.rotate32(a, 12, 'left'); + }, + }, + rot64: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.rotate64(a, 12, 'left'); }, }, leftShift: { @@ -104,7 +110,7 @@ await Bitwise.compile(); [2, 4, 8, 16, 32, 64].forEach((length) => { equivalent({ from: [uint(length)], to: field })( (x) => Fp.rot(x, 12, 'left'), - (x) => Gadgets.rotate(x, 12, 'left') + (x) => Gadgets.rotate64(x, 12, 'left') ); equivalent({ from: [uint(length)], to: field })( (x) => Fp.leftShift(x, 12), @@ -116,6 +122,13 @@ await Bitwise.compile(); ); }); +[2, 4, 8, 16, 32].forEach((length) => { + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.rot(x, 12, 'left', 32), + (x) => Gadgets.rotate32(x, 12, 'left') + ); +}); + await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs: 3 })( (x, y) => { return x ^ y; @@ -229,6 +242,21 @@ let isJustRotate = ifNotAllConstant( and(contains(rotChain), withoutGenerics(equals(rotChain))) ); -constraintSystem.fromZkProgram(Bitwise, 'rot', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'rot64', isJustRotate); + +constraintSystem.fromZkProgram( + Bitwise, + 'rot32', + ifNotAllConstant( + contains([ + 'Generic', + 'Generic', + 'EndoMulScalar', + 'EndoMulScalar', + 'Generic', + ]) + ) +); + constraintSystem.fromZkProgram(Bitwise, 'leftShift', isJustRotate); constraintSystem.fromZkProgram(Bitwise, 'rightShift', isJustRotate); From 0d5e09b7e9e88b8700508a66232f3d0d585ac53f Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:49:53 +0100 Subject: [PATCH 0731/1215] epose rot32 --- src/lib/gadgets/gadgets.ts | 91 +++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index c7e1208a7f..c332efdb64 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -5,10 +5,20 @@ import { compactMultiRangeCheck, multiRangeCheck, rangeCheck64, + rangeCheck32, } from './range-check.js'; -import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; +import { + not, + rotate32, + rotate64, + xor, + and, + leftShift, + rightShift, +} from './bitwise.js'; import { Field } from '../core.js'; import { ForeignField, Field3 } from './foreign-field.js'; +import { divMod32, addMod32 } from './arithmetic.js'; export { Gadgets }; @@ -39,6 +49,33 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, + + /** + * Asserts that the input value is in the range [0, 2^32). + * + * This function proves that the provided field element can be represented with 32 bits. + * If the field element exceeds 32 bits, an error is thrown. + * + * @param x - The value to be range-checked. + * + * @throws Throws an error if the input value exceeds 32 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * Gadgets.rangeCheck32(x); // successfully proves 32-bit range + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rangeCheck32(xLarge); // throws an error since input exceeds 32 bits + * ``` + * + * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, + * and don't pass the 32-bit check. If you want to prove that a value lies in the int64 range [-2^31, 2^31), + * you could use `rangeCheck32(x.add(1n << 31n))`. + */ + rangeCheck32(x: Field) { + return rangeCheck32(x); + }, /** * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. @@ -52,7 +89,7 @@ const Gadgets = { * **Important:** The gadget assumes that its input is at most 64 bits in size. * * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the rotation. - * To safely use `rotate()`, you need to make sure that the value passed in is range-checked to 64 bits; + * To safely use `rotate64()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#rotation) @@ -66,17 +103,55 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); - * const y = Gadgets.rotate(x, 2, 'left'); // left rotation by 2 bits - * const z = Gadgets.rotate(x, 2, 'right'); // right rotation by 2 bits + * const y = Gadgets.rotate64(x, 2, 'left'); // left rotation by 2 bits + * const z = Gadgets.rotate64(x, 2, 'right'); // right rotation by 2 bits + * y.assertEquals(0b110000); + * z.assertEquals(0b000011); + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rotate64(xLarge, 32, "left"); // throws an error since input exceeds 64 bits + * ``` + */ + rotate64(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rotate64(field, bits, direction); + }, + /** + * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, + * with the distinction that the bits are circulated to the opposite end of a 32-bit representation rather than being discarded. + * For a left rotation, this means that bits shifted off the left end reappear at the right end. + * Conversely, for a right rotation, bits shifted off the right end reappear at the left end. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * The `direction` parameter is a string that accepts either `'left'` or `'right'`, determining the direction of the rotation. + * + * **Important:** The gadget assumes that its input is at most 32 bits in size. + * + * If the input exceeds 32 bits, the gadget is invalid and fails to prove correct execution of the rotation. + * To safely use `rotate32()`, you need to make sure that the value passed in is range-checked to 32 bits; + * for example, using {@link Gadgets.rangeCheck32}. + * + * + * @param field {@link Field} element to rotate. + * @param bits amount of bits to rotate this {@link Field} element with. + * @param direction left or right rotation direction. + * + * @throws Throws an error if the input value exceeds 32 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); + * const y = Gadgets.rotate32(x, 2, 'left'); // left rotation by 2 bits + * const z = Gadgets.rotate32(x, 2, 'right'); // right rotation by 2 bits * y.assertEquals(0b110000); * z.assertEquals(0b000011); * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * Gadgets.rotate(xLarge, 32, "left"); // throws an error since input exceeds 64 bits + * Gadgets.rotate32(xLarge, 32, "left"); // throws an error since input exceeds 32 bits * ``` */ - rotate(field: Field, bits: number, direction: 'left' | 'right' = 'left') { - return rotate(field, bits, direction); + rotate32(field: Field, bits: number, direction: 'left' | 'right' = 'left') { + return rotate32(field, bits, direction); }, /** * Bitwise XOR gadget on {@link Field} elements. Equivalent to the [bitwise XOR `^` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_XOR). @@ -420,6 +495,8 @@ const Gadgets = { * **Note:** This interface does not contain any provable methods. */ Field3, + divMod32, + addMod32, }; export namespace Gadgets { From 81c90c4df844bf8a690b2e97443b0c426c3b3e0c Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:50:09 +0100 Subject: [PATCH 0732/1215] rangeCheck32 --- src/lib/gadgets/range-check.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 5b2be2c8ee..ec971d8c04 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -3,6 +3,7 @@ import { Gates } from '../gates.js'; import { bitSlice, exists, toVar, toVars } from './common.js'; export { + rangeCheck32, rangeCheck64, multiRangeCheck, compactMultiRangeCheck, @@ -12,6 +13,22 @@ export { twoLMask, }; +/** + * Asserts that x is in the range [0, 2^32) + */ +function rangeCheck32(x: Field) { + if (x.isConstant()) { + if (x.toBigInt() >= 1n << 32n) { + throw Error(`rangeCheck32: expected field to fit in 32 bits, got ${x}`); + } + return; + } + + // can we make this more efficient? its 3 gates :/ + let actual = x.rangeCheckHelper(32); + actual.assertEquals(x); +} + /** * Asserts that x is in the range [0, 2^64) */ From aafd4b89ced69008539140c1e86858e45005ba8b Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 12:50:21 +0100 Subject: [PATCH 0733/1215] add rotate32 to UInt32 --- src/lib/int.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 186a8d381b..f9025437ee 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -306,7 +306,7 @@ class UInt64 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return Gadgets.rotate(this.value, bits, direction); + return Gadgets.rotate64(this.value, bits, direction); } /** @@ -589,8 +589,7 @@ class UInt32 extends CircuitValue { } static check(x: UInt32) { - let actual = x.value.rangeCheckHelper(32); - actual.assertEquals(x.value); + Gadgets.rangeCheck32(x.value); } static toInput(x: UInt32): HashInput { return { packed: [[x.value, 32]] }; @@ -820,7 +819,7 @@ class UInt32 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return Gadgets.rotate(this.value, bits, direction); + return Gadgets.rotate32(this.value, bits, direction); } /** From eacc1e606f06f6bdf217918d46c4c88d63cd093b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 12:57:42 +0100 Subject: [PATCH 0734/1215] expose assertMul and Sum --- src/lib/gadgets/foreign-field.ts | 44 ++++++++++++++++---------------- src/lib/gadgets/gadgets.ts | 16 +++++++++++- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index b0abeff8c1..f60663e988 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -19,18 +19,11 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { - ForeignField, - Field3, - bigint3, - Sign, - split, - collapse, - weakBound, - assertMul, - Sum, - assertRank1, -}; +// external API +export { ForeignField, Field3 }; + +// internal API +export { bigint3, Sign, split, collapse, weakBound, Sum, assertMul }; /** * A 3-tuple of Fields, representing a 3-limb bigint. @@ -47,10 +40,14 @@ const ForeignField = { return sum([x, y], [-1n], f); }, sum, + Sum(x: Field3) { + return new Sum(x); + }, mul: multiply, inv: inverse, div: divide, + assertMul, }; /** @@ -157,7 +154,7 @@ function inverse(x: Field3, f: bigint): Field3 { let xInv2Bound = weakBound(xInv[2], f); let one: Field2 = [Field.from(1n), Field.from(0n)]; - assertMul(x, xInv, one, f); + assertMulInternal(x, xInv, one, f); // range check on result bound // TODO: this uses two RCs too many.. need global RC stack @@ -190,7 +187,7 @@ function divide( }); multiRangeCheck(z); let z2Bound = weakBound(z[2], f); - assertMul(z, y, x, f); + assertMulInternal(z, y, x, f); // range check on result bound multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); @@ -212,7 +209,12 @@ function divide( /** * Common logic for gadgets that expect a certain multiplication result a priori, instead of just using the remainder. */ -function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { +function assertMulInternal( + x: Field3, + y: Field3, + xy: Field3 | Field2, + f: bigint +) { let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); // range check on quotient @@ -413,10 +415,8 @@ function split2(x: bigint): [bigint, bigint] { * As usual, all values are assumed to be range checked, and the left and right multiplication inputs * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. * However, all extra checks that are needed on the _sums_ are handled here. - * - * TODO example */ -function assertRank1( +function assertMul( x: Field3 | Sum, y: Field3 | Sum, xy: Field3 | Sum, @@ -432,7 +432,7 @@ function assertRank1( // x is chained into the ffmul gate let x0 = x.finishForMulInput(f, true); - assertMul(x0, y0, xy0, f); + assertMulInternal(x0, y0, xy0, f); } class Sum { @@ -463,7 +463,7 @@ class Sum { return this; } - finishOne() { + #finishOne() { let result = this.#summands[0]; this.#result = result; return result; @@ -473,7 +473,7 @@ class Sum { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.finishOne(); + if (n === 0) return this.#finishOne(); let x = this.#summands.map(toVars); let result = x[0]; @@ -492,7 +492,7 @@ class Sum { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.finishOne(); + if (n === 0) return this.#finishOne(); let x = this.#summands.map(toVars); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index dc45693690..e5c267653b 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -8,7 +8,7 @@ import { } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; -import { ForeignField, Field3 } from './foreign-field.js'; +import { ForeignField, Field3, Sum } from './foreign-field.js'; export { Gadgets }; @@ -470,6 +470,20 @@ const Gadgets = { div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); }, + + /** + * TODO + */ + assertMul(x: Field3 | Sum, y: Field3 | Sum, z: Field3 | Sum, f: bigint) { + return ForeignField.assertMul(x, y, z, f); + }, + + /** + * TODO + */ + Sum(x: Field3) { + return ForeignField.Sum(x); + }, }, /** From 003f29d8a48b0e8f3141102dd31f7b7b7aae2d15 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 13:00:27 +0100 Subject: [PATCH 0735/1215] adapt elliptic curve gadget --- src/lib/gadgets/elliptic-curve.ts | 41 +++++++++++++------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 60d9646d5c..9b9b7417ad 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -6,14 +6,7 @@ import { import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { assert, exists } from './common.js'; -import { - Field3, - ForeignField, - Sum, - assertRank1, - split, - weakBound, -} from './foreign-field.js'; +import { Field3, ForeignField, split, weakBound } from './foreign-field.js'; import { l, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; import { @@ -88,18 +81,18 @@ function add(p1: Point, p2: Point, f: bigint) { multiRangeCheck([mBound, x3Bound, y3Bound]); // (x1 - x2)*m = y1 - y2 - let deltaX = new Sum(x1).sub(x2); - let deltaY = new Sum(y1).sub(y2); - assertRank1(deltaX, m, deltaY, f); + let deltaX = ForeignField.Sum(x1).sub(x2); + let deltaY = ForeignField.Sum(y1).sub(y2); + ForeignField.assertMul(deltaX, m, deltaY, f); // m^2 = x1 + x2 + x3 - let xSum = new Sum(x1).add(x2).add(x3); - assertRank1(m, m, xSum, f); + let xSum = ForeignField.Sum(x1).add(x2).add(x3); + ForeignField.assertMul(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 - let deltaX1X3 = new Sum(x1).sub(x3); - let ySum = new Sum(y1).add(y3); - assertRank1(deltaX1X3, m, ySum, f); + let deltaX1X3 = ForeignField.Sum(x1).sub(x3); + let ySum = ForeignField.Sum(y1).add(y3); + ForeignField.assertMul(deltaX1X3, m, ySum, f); return { x: x3, y: y3 }; } @@ -142,18 +135,18 @@ function double(p1: Point, f: bigint) { // 2*y1*m = 3*x1x1 // TODO this assumes the curve has a == 0 - let y1Times2 = new Sum(y1).add(y1); - let x1x1Times3 = new Sum(x1x1).add(x1x1).add(x1x1); - assertRank1(y1Times2, m, x1x1Times3, f); + let y1Times2 = ForeignField.Sum(y1).add(y1); + let x1x1Times3 = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); + ForeignField.assertMul(y1Times2, m, x1x1Times3, f); // m^2 = 2*x1 + x3 - let xSum = new Sum(x1).add(x1).add(x3); - assertRank1(m, m, xSum, f); + let xSum = ForeignField.Sum(x1).add(x1).add(x3); + ForeignField.assertMul(m, m, xSum, f); // (x1 - x3)*m = y1 + y3 - let deltaX1X3 = new Sum(x1).sub(x3); - let ySum = new Sum(y1).add(y3); - assertRank1(deltaX1X3, m, ySum, f); + let deltaX1X3 = ForeignField.Sum(x1).sub(x3); + let ySum = ForeignField.Sum(y1).add(y3); + ForeignField.assertMul(deltaX1X3, m, ySum, f); // bounds checks multiRangeCheck([mBound, x3Bound, y3Bound]); From 641917678aa8b84d4ca1a1d4f7fa3c150fbf2132 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 13:13:43 +0100 Subject: [PATCH 0736/1215] fix tests --- src/examples/zkprogram/gadgets.ts | 8 ++++---- src/lib/gadgets/bitwise.unit-test.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/examples/zkprogram/gadgets.ts b/src/examples/zkprogram/gadgets.ts index 0a87b61dce..1e2508c804 100644 --- a/src/examples/zkprogram/gadgets.ts +++ b/src/examples/zkprogram/gadgets.ts @@ -3,8 +3,8 @@ import { Field, Provable, Gadgets, ZkProgram } from 'o1js'; let cs = Provable.constraintSystem(() => { let f = Provable.witness(Field, () => Field(12)); - let res1 = Gadgets.rotate(f, 2, 'left'); - let res2 = Gadgets.rotate(f, 2, 'right'); + let res1 = Gadgets.rotate64(f, 2, 'left'); + let res2 = Gadgets.rotate64(f, 2, 'right'); res1.assertEquals(Field(48)); res2.assertEquals(Field(3)); @@ -21,8 +21,8 @@ const BitwiseProver = ZkProgram({ privateInputs: [], method: () => { let a = Provable.witness(Field, () => Field(48)); - let actualLeft = Gadgets.rotate(a, 2, 'left'); - let actualRight = Gadgets.rotate(a, 2, 'right'); + let actualLeft = Gadgets.rotate64(a, 2, 'left'); + let actualRight = Gadgets.rotate64(a, 2, 'right'); let expectedLeft = Field(192); actualLeft.assertEquals(expectedLeft); diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 980610e92c..b019975909 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -180,7 +180,18 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return Fp.rot(x, 12, 'left'); }, async (x) => { - let proof = await Bitwise.rot(x); + let proof = await Bitwise.rot64(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + if (x >= 2n ** 32n) throw Error('Does not fit into 32 bits'); + return Fp.rot(x, 12, 'left', 32); + }, + async (x) => { + let proof = await Bitwise.rot32(x); return proof.publicOutput; } ); From e8cac1acaa6638533f11ddd3322756b6a4267a88 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 21 Nov 2023 13:18:55 +0100 Subject: [PATCH 0737/1215] fix vk regression test --- tests/vk-regression/plain-constraint-system.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index b5d0c1f447..9ee0e2737d 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -37,10 +37,10 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { rot() { let a = Provable.witness(Field, () => new Field(12)); Gadgets.rangeCheck64(a); // `rotate()` doesn't do this - Gadgets.rotate(a, 2, 'left'); - Gadgets.rotate(a, 2, 'right'); - Gadgets.rotate(a, 4, 'left'); - Gadgets.rotate(a, 4, 'right'); + Gadgets.rotate64(a, 2, 'left'); + Gadgets.rotate64(a, 2, 'right'); + Gadgets.rotate64(a, 4, 'left'); + Gadgets.rotate64(a, 4, 'right'); }, xor() { let a = Provable.witness(Field, () => new Field(5n)); From 347524a4aa3fce103b0b98adb98cb7955a243729 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 14:30:28 +0100 Subject: [PATCH 0738/1215] add assertion to prevent invalid multiplication --- src/lib/gadgets/foreign-field.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index f60663e988..dd89d04e4c 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -426,6 +426,15 @@ function assertMul( y = Sum.fromUnfinished(y); xy = Sum.fromUnfinished(xy); + // conservative estimate to ensure that multiplication bound is satisfied + // we assume that all summands si are bounded with si[2] <= f[2] checks, which implies si < 2^k where k := ceil(log(f)) + // our assertion below gives us + // |x|*|y| + q*f + |r| < (x.length * y.length) 2^2k + 2^2k + 2^2k < 3 * 2^(2*258) < 2^264 * (native modulus) + assert( + BigInt(Math.ceil(Math.sqrt(x.length * y.length))) * f < 1n << 258n, + `Foreign modulus is too large for multiplication of sums of lengths ${x.length} and ${y.length}` + ); + // finish the y and xy sums with a zero gate let y0 = y.finishForMulInput(f); let xy0 = xy.finish(f); @@ -449,6 +458,10 @@ class Sum { return this.#result; } + get length() { + return this.#summands.length; + } + add(y: Field3) { assert(this.#result === undefined, 'sum already finished'); this.#ops.push(1n); From 24a0bf6b9b0e5db0500970184ed6d7b3b39f31a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 14:33:16 +0100 Subject: [PATCH 0739/1215] document assertMul --- src/lib/gadgets/gadgets.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e5c267653b..ec389e92ee 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -472,14 +472,39 @@ const Gadgets = { }, /** - * TODO + * Optimized multiplication of sums in a foreign field, for example: `(x - y)*z = a + b + c mod f` + * + * Note: This is much more efficient than using {@link ForeignField.add} and {@link ForeignField.sub} separately to + * compute the multiplication inputs and outputs, and then using {@link ForeignField.mul} to constrain the result. + * + * The sums passed into this gadgets are "lazy sums" created with {@link ForeignField.Sum}. + * You can also pass in plain {@link Field3} elements. + * + * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link ForeignField.mul}: + * - each summand's limbs are in the range [0, 2^88) + * - summands that are part of a multiplication input satisfy `x[2] <= f[2]` + * + * @throws if the modulus is so large that the second assumption no longer suffices for validity of the multiplication. + * For small sums and moduli < 2^256, this will not fail. + * + * @throws if the provided multiplication result is not correct modulo f. + * + * @example + * ```ts + * // we assume that x, y, z, a, b, c are range-checked, analogous to `ForeignField.mul()` + * let xMinusY = ForeignField.Sum(x).sub(y); + * let aPlusBPlusC = ForeignField.Sum(a).add(b).add(c); + * + * // assert that (x - y)*z = a + b + c mod f + * ForeignField.assertMul(xMinusY, z, aPlusBPlusC, f); + * ``` */ assertMul(x: Field3 | Sum, y: Field3 | Sum, z: Field3 | Sum, f: bigint) { return ForeignField.assertMul(x, y, z, f); }, /** - * TODO + * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. */ Sum(x: Field3) { return ForeignField.Sum(x); @@ -499,4 +524,12 @@ export namespace Gadgets { * A 3-tuple of Fields, representing a 3-limb bigint. */ export type Field3 = [Field, Field, Field]; + + export namespace ForeignField { + /** + * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + */ + export type Sum = Sum_; + } } +type Sum_ = Sum; From 021b00144ccd80a353227d4763399920e20d00a9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 16:47:50 +0100 Subject: [PATCH 0740/1215] address feedback --- src/lib/gadgets/foreign-field.ts | 21 +++++++++++---------- src/lib/gadgets/gadgets.ts | 4 ++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index fc30ef0a4e..75f41a7f82 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -85,7 +85,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { let y_ = toBigint3(y); // figure out if there's overflow - let r = collapse(x_) + sign * collapse(y_); + let r = combine(x_) + sign * combine(y_); let overflow = 0n; if (sign === 1n && r >= f) overflow = 1n; if (sign === -1n && r < 0n) overflow = -1n; @@ -93,7 +93,7 @@ function singleAdd(x: Field3, y: Field3, sign: Sign, f: bigint) { // do the add with carry // note: this "just works" with negative r01 - let r01 = collapse2(x_) + sign * collapse2(y_) - overflow * collapse2(f_); + let r01 = combine2(x_) + sign * combine2(y_) - overflow * combine2(f_); let carry = r01 >> l2; r01 &= l2Mask; let [r0, r1] = split2(r01); @@ -141,6 +141,7 @@ function inverse(x: Field3, f: bigint): Field3 { return xInv === undefined ? [0n, 0n, 0n] : split(xInv); }); multiRangeCheck(xInv); + // 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)]; @@ -189,7 +190,7 @@ function divide( let y01 = y[0].add(y[1].mul(1n << l)); y01.equals(0n).and(y[2].equals(0n)).assertFalse(); let [f0, f1, f2] = split(f); - let f01 = collapse2([f0, f1]); + let f01 = combine2([f0, f1]); y01.equals(f01).and(y[2].equals(f2)).assertFalse(); } @@ -233,13 +234,13 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { let [b0, b1, b2] = toBigint3(b); // compute q and r such that a*b = q*f + r - let ab = collapse([a0, a1, a2]) * collapse([b0, b1, b2]); + let ab = combine([a0, a1, a2]) * combine([b0, b1, b2]); let q = ab / f; let r = ab - q * f; let [q0, q1, q2] = split(q); let [r0, r1, r2] = split(r); - let r01 = collapse2([r0, r1]); + let r01 = combine2([r0, r1]); // compute product terms let p0 = a0 * b0 + q0 * f_0; @@ -247,7 +248,7 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { let p2 = a0 * b2 + a1 * b1 + a2 * b0 + q0 * f_2 + q1 * f_1 + q2 * f_0; let [p10, p110, p111] = split(p1); - let p11 = collapse2([p110, p111]); + let p11 = combine2([p110, p111]); // carry bottom limbs let c0 = (p0 + (p10 << l) - r01) >> l2; @@ -337,7 +338,7 @@ const Field3 = { * Turn a 3-tuple of Fields into a bigint */ toBigint(x: Field3): bigint { - return collapse(toBigint3(x)); + return combine(toBigint3(x)); }, /** @@ -352,7 +353,7 @@ const Field3 = { type Field2 = [Field, Field]; const Field2 = { toBigint(x: Field2): bigint { - return collapse2(Tuple.map(x, (x) => x.toBigInt())); + return combine2(Tuple.map(x, (x) => x.toBigInt())); }, }; @@ -360,14 +361,14 @@ function toBigint3(x: Field3): bigint3 { return Tuple.map(x, (x) => x.toBigInt()); } -function collapse([x0, x1, x2]: bigint3) { +function combine([x0, x1, x2]: bigint3) { return x0 + (x1 << l) + (x2 << l2); } function split(x: bigint): bigint3 { return [x & lMask, (x >> l) & lMask, (x >> l2) & lMask]; } -function collapse2([x0, x1]: bigint3 | [bigint, bigint]) { +function combine2([x0, x1]: bigint3 | [bigint, bigint]) { return x0 + (x1 << l); } function split2(x: bigint): [bigint, bigint] { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index dc45693690..b67ef58fd9 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -370,6 +370,8 @@ const Gadgets = { * Foreign field subtraction: `x - y mod f` * * See {@link ForeignField.add} for assumptions and usage examples. + * + * @throws fails if `x - y < -f`, where the result cannot be brought back to a positive number by adding `f` once. */ sub(x: Field3, y: Field3, f: bigint) { return ForeignField.sub(x, y, f); @@ -466,6 +468,8 @@ const Gadgets = { * See {@link ForeignField.mul} for assumptions on inputs and usage examples. * * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. + * + * @throws Different than {@link ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. */ div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); From bdd3efee0353011f5244aa33cd1e3434a67a98da Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 18:55:22 +0100 Subject: [PATCH 0741/1215] fix ts build --- src/lib/gadgets/bitwise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 7d112664e7..4a8a66f041 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -212,7 +212,7 @@ function rotate( field.toBigInt() < 2n ** BigInt(MAX_BITS), `rotation: expected field to be at most 64 bits, got ${field.toBigInt()}` ); - return new Field(Fp.rot(field.toBigInt(), bits, direction)); + return new Field(Fp.rot(field.toBigInt(), BigInt(bits), direction)); } const [rotated] = rot(field, bits, direction); return rotated; From f5719d9a599986a171ea3c90eaa731c3497760ad Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 18:55:32 +0100 Subject: [PATCH 0742/1215] fix mina tests --- src/tests/inductive-proofs-small.ts | 13 +------------ src/tests/inductive-proofs.ts | 13 +------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/tests/inductive-proofs-small.ts b/src/tests/inductive-proofs-small.ts index 2441c0030d..acde6a86f6 100644 --- a/src/tests/inductive-proofs-small.ts +++ b/src/tests/inductive-proofs-small.ts @@ -1,15 +1,6 @@ -import { - SelfProof, - Field, - ZkProgram, - isReady, - shutdown, - Proof, -} from '../index.js'; +import { SelfProof, Field, ZkProgram, Proof } from 'o1js'; import { tic, toc } from '../examples/utils/tic-toc.node.js'; -await isReady; - let MaxProofsVerifiedOne = ZkProgram({ name: 'recursive-1', publicInput: Field, @@ -85,5 +76,3 @@ function testJsonRoundtrip(ProofClass: any, proof: Proof) { ); return ProofClass.fromJSON(jsonProof); } - -shutdown(); diff --git a/src/tests/inductive-proofs.ts b/src/tests/inductive-proofs.ts index e60eee018a..101487dae5 100644 --- a/src/tests/inductive-proofs.ts +++ b/src/tests/inductive-proofs.ts @@ -1,15 +1,6 @@ -import { - SelfProof, - Field, - ZkProgram, - isReady, - shutdown, - Proof, -} from '../index.js'; +import { SelfProof, Field, ZkProgram, Proof } from 'o1js'; import { tic, toc } from '../examples/utils/tic-toc.node.js'; -await isReady; - let MaxProofsVerifiedZero = ZkProgram({ name: 'no-recursion', publicInput: Field, @@ -155,5 +146,3 @@ function testJsonRoundtrip(ProofClass: any, proof: Proof) { ); return ProofClass.fromJSON(jsonProof); } - -shutdown(); From 005ae5dbce07f3e2dcae337386b26986439c83db Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 18:55:51 +0100 Subject: [PATCH 0743/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index c8f8c631f2..87996f7d27 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c8f8c631f28b84c3d3859378a2fe857091207755 +Subproject commit 87996f7d27b37208d536349ab9449047964736f2 From f9429cb76671812c83295a4cb8d624ce8046130f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 19:03:18 +0100 Subject: [PATCH 0744/1215] fixup --- src/lib/gadgets/bitwise.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 559cea7e2a..91b9d138f0 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -103,7 +103,7 @@ await Bitwise.compile(); [2, 4, 8, 16, 32, 64].forEach((length) => { equivalent({ from: [uint(length)], to: field })( - (x) => Fp.rot(x, 12, 'left'), + (x) => Fp.rot(x, 12n, 'left'), (x) => Gadgets.rotate(x, 12, 'left') ); equivalent({ from: [uint(length)], to: field })( @@ -164,7 +164,7 @@ await equivalentAsync( await equivalentAsync({ from: [field], to: field }, { runs: 3 })( (x) => { if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); - return Fp.rot(x, 12, 'left'); + return Fp.rot(x, 12n, 'left'); }, async (x) => { let proof = await Bitwise.rot(x); From 1ad7333e9e35ba455c3be8696544f1909cb7b685 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 21 Nov 2023 10:19:32 -0800 Subject: [PATCH 0745/1215] chore(package.json): bump version from 0.14.1 to 0.14.2 for new release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93ea97ca43..eff466f3fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.14.1", + "version": "0.14.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.14.1", + "version": "0.14.2", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 8e748d808f..6d880daea0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.14.1", + "version": "0.14.2", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From 2fefe982a5e22f537b278eaca550d5032cdad607 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 19:25:31 +0100 Subject: [PATCH 0746/1215] fixup mina test --- run-minimal-mina-tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run-minimal-mina-tests.sh b/run-minimal-mina-tests.sh index 3aae238cce..d6380585f2 100755 --- a/run-minimal-mina-tests.sh +++ b/run-minimal-mina-tests.sh @@ -1,4 +1,6 @@ #!/bin/bash set -e +npm run dev + ./run src/tests/inductive-proofs-small.ts --bundle From e54ec52b9721704204721ea17953ba478717167a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 21 Nov 2023 10:24:18 -0800 Subject: [PATCH 0747/1215] docs(CHANGELOG.md): update unreleased section link and add version 0.14.2 section to reflect recent changes in the project --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 533c57bbcf..7a02ed147d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/26363465d...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) + +> No unreleased changes yet + +## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) ### Breaking changes From a88276c2267626ce9e550ba25d8bdc357e199143 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 21:26:14 +0100 Subject: [PATCH 0748/1215] delete ec gadget from this branch --- src/lib/gadgets/elliptic-curve.ts | 79 ------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/lib/gadgets/elliptic-curve.ts diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts deleted file mode 100644 index b52eda448f..0000000000 --- a/src/lib/gadgets/elliptic-curve.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { inverse, mod } from '../../bindings/crypto/finite_field.js'; -import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; -import { Field } from '../field.js'; -import { Provable } from '../provable.js'; -import { exists } from './common.js'; -import { - Field3, - Sum, - assertRank1, - bigint3, - split, - weakBound, -} from './foreign-field.js'; -import { multiRangeCheck } from './range-check.js'; -import { printGates } from '../testing/constraint-system.js'; - -type Point = { x: Field3; y: Field3 }; -type point = { x: bigint3; y: bigint3; infinity: boolean }; - -function add({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, f: bigint) { - // TODO constant case - - // witness and range-check slope, x3, y3 - let witnesses = exists(9, () => { - let [x1_, x2_, y1_, y2_] = Field3.toBigints(x1, x2, y1, y2); - let denom = inverse(mod(x1_ - x2_, f), f); - - let m = denom !== undefined ? mod((y1_ - y2_) * denom, f) : 0n; - let m2 = mod(m * m, f); - let x3 = mod(m2 - x1_ - x2_, f); - let y3 = mod(m * (x1_ - x3) - y1_, f); - - return [...split(m), ...split(x3), ...split(y3)]; - }); - let [m0, m1, m2, x30, x31, x32, y30, y31, y32] = witnesses; - let m: Field3 = [m0, m1, m2]; - let x3: Field3 = [x30, x31, x32]; - let y3: Field3 = [y30, y31, y32]; - - multiRangeCheck(m); - multiRangeCheck(x3); - multiRangeCheck(y3); - let m2Bound = weakBound(m[2], f); - let x3Bound = weakBound(x3[2], f); - // we dont need to bounds-check y3[2] because it's never one of the inputs to a multiplication - - // (x1 - x2)*m = y1 - y2 - let deltaX = new Sum(x1).sub(x2); - let deltaY = new Sum(y1).sub(y2); - let qBound1 = assertRank1(deltaX, m, deltaY, f); - - // m^2 = x1 + x2 + x3 - let xSum = new Sum(x1).add(x2).add(x3); - let qBound2 = assertRank1(m, m, xSum, f); - - // (x1 - x3)*m = y1 + y3 - let deltaX1X3 = new Sum(x1).sub(x3); - let ySum = new Sum(y1).add(y3); - let qBound3 = assertRank1(deltaX1X3, m, ySum, f); - - // bounds checks - multiRangeCheck([m2Bound, x3Bound, qBound1]); - multiRangeCheck([qBound2, qBound3, Field.from(0n)]); -} - -let cs = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - - let g = { x: x1, y: y1 }; - let h = { x: x2, y: y2 }; - - add(g, h, exampleFields.secp256k1.modulus); -}); - -printGates(cs.gates); -console.log({ digest: cs.digest, rows: cs.rows }); From b2cada899fa96eeed5147d587f19ac732c039a8d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 21:27:39 +0100 Subject: [PATCH 0749/1215] adapt to main --- src/lib/gadgets/foreign-field.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index c02a3796dc..0d55a9b956 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -23,7 +23,7 @@ export { bigint3, Sign, split, - collapse, + combine, weakBound, assertMul, Sum, @@ -423,13 +423,11 @@ function assertRank1( // x is chained into the ffmul gate let x0 = x.finishForChaining(f); - let q2Bound = assertMul(x0, y0, xy0, f); + assertMul(x0, y0, xy0, f); // we need an extra range check on x and y, but not xy x.rangeCheck(); y.rangeCheck(); - - return q2Bound; } class Sum { From ccb92466a209ece14c6893fabec741006e6885b2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 20 Nov 2023 22:02:02 +0100 Subject: [PATCH 0750/1215] replace mul input rcs with generic gates --- src/lib/gadgets/foreign-field.ts | 106 +++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 0d55a9b956..7bd6ae6d77 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,6 +5,7 @@ import { import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; +import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { assert, bitSlice, exists, toVars } from './common.js'; import { @@ -396,14 +397,15 @@ function split2(x: bigint): [bigint, bigint] { /** * Optimized multiplication of sums, like (x + y)*z = a + b + c * - * We use two optimizations over naive summing and then multiplying: + * We use several optimizations over naive summing and then multiplying: * * - we skip the range check on the remainder sum, because ffmul is sound with r being a sum of range-checked values + * - we replace the range check on the input sums with an extra low limb sum using generic gates * - we chain the first input's sum into the ffmul gate * * As usual, all values are assumed to be range checked, and the left and right multiplication inputs * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. - * However, all extra checks that are needed on the sums are handled here. + * However, all extra checks that are needed on the _sums_ are handled here. * * TODO example */ @@ -413,19 +415,19 @@ function assertRank1( xy: Field3 | Sum, f: bigint ) { - x = Sum.fromUnfinished(x, f); - y = Sum.fromUnfinished(y, f); - xy = Sum.fromUnfinished(xy, f); + x = Sum.fromUnfinished(x); + y = Sum.fromUnfinished(y); + xy = Sum.fromUnfinished(xy); // finish the y and xy sums with a zero gate let y0 = y.finish(f); let xy0 = xy.finish(f); // x is chained into the ffmul gate - let x0 = x.finishForChaining(f); + let x0 = x.finish(f, true); assertMul(x0, y0, xy0, f); - // we need an extra range check on x and y, but not xy + // we need and extra range check on x and y x.rangeCheck(); y.rangeCheck(); } @@ -458,10 +460,17 @@ class Sum { return this; } - finish(f: bigint, forChaining = false) { + finishOne() { + let result = this.#summands[0]; + this.#result = result; + return result; + } + + finish(f: bigint, isChained = false) { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; + if (n === 0) return this.finishOne(); let x = this.#summands.map(toVars); let result = x[0]; @@ -469,14 +478,87 @@ class Sum { for (let i = 0; i < n; i++) { ({ result } = singleAdd(result, x[i + 1], signs[i], f)); } - if (n > 0 && !forChaining) Gates.zero(...result); + if (!isChained) Gates.zero(...result); this.#result = result; return result; } - finishForChaining(f: bigint) { - return this.finish(f, true); + finishForMulInput(f: bigint, isChained = false) { + assert(this.#result === undefined, 'sum already finished'); + let signs = this.#ops; + let n = signs.length; + if (n === 0) return this.finishOne(); + + let x = this.#summands.map(toVars); + + // since the sum becomes a multiplication input, we need to constrain all limbs _individually_. + // sadly, ffadd only constrains the low and middle limb together. + // we could fix it with a RC just for the lower two limbs + // but it's cheaper to add generic gates which handle the lowest limb separately, and avoids the unfilled MRC slot + let f_ = split(f); + + // compute witnesses for generic gates -- overflows and carries + let nFields = Provable.Array(Field, n); + let [overflows, carries] = Provable.witness( + provableTuple([nFields, nFields]), + () => { + let overflows: bigint[] = []; + let carries: bigint[] = []; + + let r = Field3.toBigint(x[0]); + + for (let i = 0; i < n; i++) { + // this duplicates some of the logic in singleAdd + let x_ = split(r); + let y_ = toBigint3(x[i + 1]); + let sign = signs[i]; + + // figure out if there's overflow + r = r + sign * combine(y_); + let overflow = 0n; + if (sign === 1n && r >= f) overflow = 1n; + if (sign === -1n && r < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; + overflows.push(overflow); + + // add with carry, only on the lowest limb + let r0 = x_[0] + sign * y_[0] - overflow * f_[0]; + carries.push(r0 >> l); + } + return [overflows.map(Field.from), carries.map(Field.from)]; + } + ); + + // generic gates for low limbs + let result0 = x[0][0]; + let r0s: Field[] = []; + for (let i = 0; i < n; i++) { + // constrain carry to 0, 1, or -1 + let c = carries[i]; + c.mul(c.sub(1n)).mul(c.add(1n)).assertEquals(0n); + + result0 = result0 + .add(x[i + 1][0].mul(signs[i])) + .add(overflows[i].mul(f_[0])) + .sub(c.mul(1n << l)) + .seal(); + r0s.push(result0); + } + + // ffadd chain + let result = x[0]; + for (let i = 0; i < n; i++) { + let r = singleAdd(result, x[i + 1], signs[i], f); + // wire low limb and overflow to previous values + r.result[0].assertEquals(r0s[i]); + r.overflow.assertEquals(overflows[i]); + result = r.result; + } + if (!isChained) Gates.zero(...result); + + this.#result = result; + return result; } rangeCheck() { @@ -484,7 +566,7 @@ class Sum { if (this.#ops.length > 0) multiRangeCheck(this.#result); } - static fromUnfinished(x: Field3 | Sum, f: bigint) { + static fromUnfinished(x: Field3 | Sum) { if (x instanceof Sum) { assert(x.#result === undefined, 'sum already finished'); return x; From 554f37a5c94e174e81b992b9fd7d4ce9afff8398 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:03:43 +0100 Subject: [PATCH 0751/1215] enable strict typing of variable fields --- src/lib/field.ts | 15 +++++++++++++-- src/lib/gadgets/common.ts | 16 ++++++++++------ src/snarky.d.ts | 8 ++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index de52ecf621..9e2497e231 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -11,10 +11,12 @@ export { Field }; // internal API export { - ConstantField, FieldType, FieldVar, FieldConst, + ConstantField, + VarField, + VarFieldVar, isField, withMessage, readVarMessage, @@ -69,6 +71,7 @@ type FieldVar = | [FieldType.Scale, FieldConst, FieldVar]; type ConstantFieldVar = [FieldType.Constant, FieldConst]; +type VarFieldVar = [FieldType.Var, number]; const FieldVar = { constant(x: bigint | FieldConst): ConstantFieldVar { @@ -78,6 +81,9 @@ const FieldVar = { isConstant(x: FieldVar): x is ConstantFieldVar { return x[0] === FieldType.Constant; }, + isVar(x: FieldVar): x is VarFieldVar { + return x[0] === FieldType.Var; + }, add(x: FieldVar, y: FieldVar): FieldVar { if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; @@ -101,6 +107,7 @@ const FieldVar = { }; type ConstantField = Field & { value: ConstantFieldVar }; +type VarField = Field & { value: VarFieldVar }; /** * A {@link Field} is an element of a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field). @@ -1039,7 +1046,7 @@ class Field { seal() { if (this.isConstant()) return this; let x = Snarky.field.seal(this.value); - return new Field(x); + return VarField(x); } /** @@ -1360,3 +1367,7 @@ there is \`Provable.asProver(() => { ... })\` which allows you to use ${varName} Warning: whatever happens inside asProver() will not be part of the zk proof. `; } + +function VarField(x: VarFieldVar): VarField { + return new Field(x) as VarField; +} diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 196ca64e73..1b52023ab1 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,5 +1,5 @@ import { Provable } from '../provable.js'; -import { Field, FieldConst, FieldVar, FieldType } from '../field.js'; +import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; import { MlArray } from '../ml/base.js'; @@ -21,7 +21,7 @@ export { function existsOne(compute: () => bigint) { let varMl = Snarky.existsVar(() => FieldConst.fromBigint(compute())); - return new Field(varMl); + return VarField(varMl); } function exists TupleN>( @@ -31,7 +31,7 @@ function exists TupleN>( let varsMl = Snarky.exists(n, () => MlArray.mapTo(compute(), FieldConst.fromBigint) ); - let vars = MlArray.mapFrom(varsMl, (v) => new Field(v)); + let vars = MlArray.mapFrom(varsMl, VarField); return TupleN.fromArray(n, vars); } @@ -43,20 +43,24 @@ function exists TupleN>( * * Same as `Field.seal()` with the difference that `seal()` leaves constants as is. */ -function toVar(x: Field | bigint) { +function toVar(x: Field | bigint): VarField { // don't change existing vars - if (x instanceof Field && x.value[1] === FieldType.Var) return x; + if (isVar(x)) return x; let xVar = existsOne(() => Field.from(x).toBigInt()); xVar.assertEquals(x); return xVar; } +function isVar(x: Field | bigint): x is VarField { + return x instanceof Field && FieldVar.isVar(x.value); +} + /** * Apply {@link toVar} to each element of a tuple. */ function toVars>( fields: T -): { [k in keyof T]: Field } { +): { [k in keyof T]: VarField } { return Tuple.map(fields, toVar); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 69fe3bcb37..343d714c46 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,5 +1,5 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; -import type { Field, FieldConst, FieldVar } from './lib/field.js'; +import type { Field, FieldConst, FieldVar, VarFieldVar } from './lib/field.js'; import type { BoolVar, Bool } from './lib/bool.js'; import type { ScalarConst } from './lib/scalar.js'; import type { @@ -181,11 +181,11 @@ declare const Snarky: { exists( sizeInFields: number, compute: () => MlArray - ): MlArray; + ): MlArray; /** * witness a single field element variable */ - existsVar(compute: () => FieldConst): FieldVar; + existsVar(compute: () => FieldConst): VarFieldVar; /** * APIs that have to do with running provable code @@ -281,7 +281,7 @@ declare const Snarky: { * returns a new witness from an AST * (implemented with toConstantAndTerms) */ - seal(x: FieldVar): FieldVar; + seal(x: FieldVar): VarFieldVar; /** * Unfolds AST to get `x = c + c0*Var(i0) + ... + cn*Var(in)`, * returns `(c, [(c0, i0), ..., (cn, in)])`; From e7c1791ad23b9785b36218a82b4ab0b3fd24ebde Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 21:32:53 +0100 Subject: [PATCH 0752/1215] optimized assertOneOf gadget --- src/lib/gadgets/basic.ts | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/lib/gadgets/basic.ts diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts new file mode 100644 index 0000000000..4d16d0896e --- /dev/null +++ b/src/lib/gadgets/basic.ts @@ -0,0 +1,73 @@ +import type { Field, VarField } from '../field.js'; +import { existsOne, toVar } from './common.js'; +import { Gates } from '../gates.js'; +import { TupleN } from '../util/types.js'; + +export { assertOneOf }; + +// TODO: create constant versions of these and expose on Gadgets + +/** + * Assert that a value equals one of a finite list of constants: + * `(x - c1)*(x - c2)*...*(x - cn) === 0` + * + * TODO: what prevents us from getting the same efficiency with snarky DSL code? + */ +function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { + let xv = toVar(x); + let [c1, c2, ...c] = allowed; + let n = c.length; + if (n === 0) { + // (x - c1)*(x - c2) === 0 + assertBilinearZero(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + return; + } + // z = (x - c1)*(x - c2) + let z = bilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + + for (let i = 0; i < n; i++) { + if (i < n - 1) { + // z = z*(x - c) + z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); + } else { + // z*(x - c) === 0 + assertBilinearZero(z, xv, [1n, -c[i], 0n, 0n]); + } + } +} + +// low-level helpers to create generic gates + +/** + * Compute bilinear function of x and y: + * z = a*x*y + b*x + c*y + d + */ +function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { + let z = existsOne(() => { + let x0 = x.toBigInt(); + let y0 = y.toBigInt(); + return a * x0 * y0 + b * x0 + c * y0 + d; + }); + // b*x + c*y - z + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: -1n, mul: a, const: d }, + { left: x, right: y, out: z } + ); + return z; +} + +/** + * Assert bilinear equation on x and y: + * a*x*y + b*x + c*y + d === 0 + */ +function assertBilinearZero( + x: VarField, + y: VarField, + [a, b, c, d]: TupleN +) { + // b*x + c*y + a*x*y + d === 0 + Gates.generic( + { left: b, right: c, out: 0n, mul: a, const: d }, + { left: x, right: y, out: x } + ); +} From 1a72bfc5832bf019bf0e13316b2cb131e88bf0e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:30:33 +0100 Subject: [PATCH 0753/1215] make new assertRank1 more efficient and enable it --- src/lib/gadgets/foreign-field.ts | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 7bd6ae6d77..07e6a7b657 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -7,7 +7,8 @@ import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; -import { assert, bitSlice, exists, toVars } from './common.js'; +import { assertOneOf } from './basic.js'; +import { assert, bitSlice, exists, toVar, toVars } from './common.js'; import { l, lMask, @@ -420,16 +421,12 @@ function assertRank1( xy = Sum.fromUnfinished(xy); // finish the y and xy sums with a zero gate - let y0 = y.finish(f); + let y0 = y.finishForMulInput(f); let xy0 = xy.finish(f); // x is chained into the ffmul gate - let x0 = x.finish(f, true); + let x0 = x.finishForMulInput(f, true); assertMul(x0, y0, xy0, f); - - // we need and extra range check on x and y - x.rangeCheck(); - y.rangeCheck(); } class Sum { @@ -484,6 +481,7 @@ class Sum { return result; } + // TODO this is complex and should be removed once we fix the ffadd gate to constrain all limbs individually finishForMulInput(f: bigint, isChained = false) { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; @@ -531,19 +529,21 @@ class Sum { ); // generic gates for low limbs - let result0 = x[0][0]; - let r0s: Field[] = []; + let x0 = x[0][0]; + let x0s: Field[] = []; for (let i = 0; i < n; i++) { // constrain carry to 0, 1, or -1 let c = carries[i]; - c.mul(c.sub(1n)).mul(c.add(1n)).assertEquals(0n); - - result0 = result0 - .add(x[i + 1][0].mul(signs[i])) - .add(overflows[i].mul(f_[0])) - .sub(c.mul(1n << l)) - .seal(); - r0s.push(result0); + assertOneOf(c, [0n, 1n, -1n]); + + // x0 <- x0 + s*y0 - o*f0 - c*2^l + x0 = toVar( + x0 + .add(x[i + 1][0].mul(signs[i])) + .sub(overflows[i].mul(f_[0])) + .sub(c.mul(1n << l)) + ); + x0s.push(x0); } // ffadd chain @@ -551,7 +551,7 @@ class Sum { for (let i = 0; i < n; i++) { let r = singleAdd(result, x[i + 1], signs[i], f); // wire low limb and overflow to previous values - r.result[0].assertEquals(r0s[i]); + r.result[0].assertEquals(x0s[i]); r.overflow.assertEquals(overflows[i]); result = r.result; } From 0ed257af20edb9d62dfa021620fcfb3b798ff354 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 12:57:42 +0100 Subject: [PATCH 0754/1215] expose assertMul and Sum --- src/lib/gadgets/foreign-field.ts | 44 ++++++++++++++++---------------- src/lib/gadgets/gadgets.ts | 16 +++++++++++- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 07e6a7b657..59d38297e3 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -19,18 +19,11 @@ import { compactMultiRangeCheck, } from './range-check.js'; -export { - ForeignField, - Field3, - bigint3, - Sign, - split, - combine, - weakBound, - assertMul, - Sum, - assertRank1, -}; +// external API +export { ForeignField, Field3 }; + +// internal API +export { bigint3, Sign, split, combine, weakBound, Sum, assertMul }; /** * A 3-tuple of Fields, representing a 3-limb bigint. @@ -47,10 +40,14 @@ const ForeignField = { return sum([x, y], [-1n], f); }, sum, + Sum(x: Field3) { + return new Sum(x); + }, mul: multiply, inv: inverse, div: divide, + assertMul, }; /** @@ -158,7 +155,7 @@ function inverse(x: Field3, f: bigint): Field3 { let xInv2Bound = weakBound(xInv[2], f); let one: Field2 = [Field.from(1n), Field.from(0n)]; - assertMul(x, xInv, one, f); + assertMulInternal(x, xInv, one, f); // range check on result bound // TODO: this uses two RCs too many.. need global RC stack @@ -191,7 +188,7 @@ function divide( }); multiRangeCheck(z); let z2Bound = weakBound(z[2], f); - assertMul(z, y, x, f); + assertMulInternal(z, y, x, f); // range check on result bound multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); @@ -213,7 +210,12 @@ function divide( /** * Common logic for gadgets that expect a certain multiplication result a priori, instead of just using the remainder. */ -function assertMul(x: Field3, y: Field3, xy: Field3 | Field2, f: bigint) { +function assertMulInternal( + x: Field3, + y: Field3, + xy: Field3 | Field2, + f: bigint +) { let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); // range check on quotient @@ -407,10 +409,8 @@ function split2(x: bigint): [bigint, bigint] { * As usual, all values are assumed to be range checked, and the left and right multiplication inputs * are assumed to be bounded such that `l * r < 2^264 * (native modulus)`. * However, all extra checks that are needed on the _sums_ are handled here. - * - * TODO example */ -function assertRank1( +function assertMul( x: Field3 | Sum, y: Field3 | Sum, xy: Field3 | Sum, @@ -426,7 +426,7 @@ function assertRank1( // x is chained into the ffmul gate let x0 = x.finishForMulInput(f, true); - assertMul(x0, y0, xy0, f); + assertMulInternal(x0, y0, xy0, f); } class Sum { @@ -457,7 +457,7 @@ class Sum { return this; } - finishOne() { + #finishOne() { let result = this.#summands[0]; this.#result = result; return result; @@ -467,7 +467,7 @@ class Sum { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.finishOne(); + if (n === 0) return this.#finishOne(); let x = this.#summands.map(toVars); let result = x[0]; @@ -486,7 +486,7 @@ class Sum { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.finishOne(); + if (n === 0) return this.#finishOne(); let x = this.#summands.map(toVars); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b67ef58fd9..b24f560af9 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -8,7 +8,7 @@ import { } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; -import { ForeignField, Field3 } from './foreign-field.js'; +import { ForeignField, Field3, Sum } from './foreign-field.js'; export { Gadgets }; @@ -474,6 +474,20 @@ const Gadgets = { div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); }, + + /** + * TODO + */ + assertMul(x: Field3 | Sum, y: Field3 | Sum, z: Field3 | Sum, f: bigint) { + return ForeignField.assertMul(x, y, z, f); + }, + + /** + * TODO + */ + Sum(x: Field3) { + return ForeignField.Sum(x); + }, }, /** From 9647ac84b31d0e422453d34b301801950bf49468 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 14:30:28 +0100 Subject: [PATCH 0755/1215] add assertion to prevent invalid multiplication --- src/lib/gadgets/foreign-field.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 59d38297e3..23835e7727 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -420,6 +420,15 @@ function assertMul( y = Sum.fromUnfinished(y); xy = Sum.fromUnfinished(xy); + // conservative estimate to ensure that multiplication bound is satisfied + // we assume that all summands si are bounded with si[2] <= f[2] checks, which implies si < 2^k where k := ceil(log(f)) + // our assertion below gives us + // |x|*|y| + q*f + |r| < (x.length * y.length) 2^2k + 2^2k + 2^2k < 3 * 2^(2*258) < 2^264 * (native modulus) + assert( + BigInt(Math.ceil(Math.sqrt(x.length * y.length))) * f < 1n << 258n, + `Foreign modulus is too large for multiplication of sums of lengths ${x.length} and ${y.length}` + ); + // finish the y and xy sums with a zero gate let y0 = y.finishForMulInput(f); let xy0 = xy.finish(f); @@ -443,6 +452,10 @@ class Sum { return this.#result; } + get length() { + return this.#summands.length; + } + add(y: Field3) { assert(this.#result === undefined, 'sum already finished'); this.#ops.push(1n); From fe166c011ee076e2adfdea19be73d4f40684bce9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 14:33:16 +0100 Subject: [PATCH 0756/1215] document assertMul --- src/lib/gadgets/gadgets.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b24f560af9..a35e94e566 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -476,14 +476,39 @@ const Gadgets = { }, /** - * TODO + * Optimized multiplication of sums in a foreign field, for example: `(x - y)*z = a + b + c mod f` + * + * Note: This is much more efficient than using {@link ForeignField.add} and {@link ForeignField.sub} separately to + * compute the multiplication inputs and outputs, and then using {@link ForeignField.mul} to constrain the result. + * + * The sums passed into this gadgets are "lazy sums" created with {@link ForeignField.Sum}. + * You can also pass in plain {@link Field3} elements. + * + * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link ForeignField.mul}: + * - each summand's limbs are in the range [0, 2^88) + * - summands that are part of a multiplication input satisfy `x[2] <= f[2]` + * + * @throws if the modulus is so large that the second assumption no longer suffices for validity of the multiplication. + * For small sums and moduli < 2^256, this will not fail. + * + * @throws if the provided multiplication result is not correct modulo f. + * + * @example + * ```ts + * // we assume that x, y, z, a, b, c are range-checked, analogous to `ForeignField.mul()` + * let xMinusY = ForeignField.Sum(x).sub(y); + * let aPlusBPlusC = ForeignField.Sum(a).add(b).add(c); + * + * // assert that (x - y)*z = a + b + c mod f + * ForeignField.assertMul(xMinusY, z, aPlusBPlusC, f); + * ``` */ assertMul(x: Field3 | Sum, y: Field3 | Sum, z: Field3 | Sum, f: bigint) { return ForeignField.assertMul(x, y, z, f); }, /** - * TODO + * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. */ Sum(x: Field3) { return ForeignField.Sum(x); @@ -503,4 +528,12 @@ export namespace Gadgets { * A 3-tuple of Fields, representing a 3-limb bigint. */ export type Field3 = [Field, Field, Field]; + + export namespace ForeignField { + /** + * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + */ + export type Sum = Sum_; + } } +type Sum_ = Sum; From e647ae46607de380b99048dc74f13e715e3d3f2d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 21:54:36 +0100 Subject: [PATCH 0757/1215] start writing unit test --- src/lib/gadgets/foreign-field.unit-test.ts | 50 ++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 4cb0d2d975..88753cdb6a 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -7,6 +7,7 @@ import { equivalentProvable, fromRandom, record, + unit, } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; import { Gadgets } from './gadgets.js'; @@ -86,6 +87,13 @@ for (let F of fields) { 'div' ); + // assert mul + equivalentProvable({ from: [f, f], to: unit })( + (x, y) => assertMulExampleNaive(Field3.from(x), Field3.from(y), F.modulus), + (x, y) => assertMulExample(x, y, F.modulus), + 'assertMul' + ); + // tests with inputs that aren't reduced mod f 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 @@ -219,9 +227,11 @@ constraintSystem.fromZkProgram(ffProgram, 'div', invLayout); // tests with proving +const runs = 3; + await ffProgram.compile(); -await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 3 })( +await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs })( (xs) => sum(xs, signs, F), async (xs) => { let proof = await ffProgram.sumchain(xs); @@ -231,7 +241,7 @@ await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs: 3 })( 'prove chain' ); -await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( +await equivalentAsync({ from: [f, f], to: f }, { runs })( F.mul, async (x, y) => { let proof = await ffProgram.mul(x, y); @@ -241,7 +251,7 @@ await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( 'prove mul' ); -await equivalentAsync({ from: [f, f], to: f }, { runs: 3 })( +await equivalentAsync({ from: [f, f], to: f }, { runs })( (x, y) => F.div(x, y) ?? throwError('no inverse'), async (x, y) => { let proof = await ffProgram.div(x, y); @@ -261,6 +271,40 @@ function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { return sum; } +// assert mul example +// (x - y) * (x + y) = x^2 - y^2 + +function assertMulExample(x: Gadgets.Field3, y: Gadgets.Field3, f: bigint) { + // witness x^2, y^2 + let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + + // assert (x - y) * (x + y) = x^2 - y^2 + let xMinusY = ForeignField.Sum(x).sub(y); + let xPlusY = ForeignField.Sum(x).add(y); + let x2MinusY2 = ForeignField.Sum(x2).sub(y2); + ForeignField.assertMul(xMinusY, xPlusY, x2MinusY2, f); +} + +function assertMulExampleNaive( + x: Gadgets.Field3, + y: Gadgets.Field3, + f: bigint +) { + // witness x^2, y^2 + let x2 = Provable.witness(Field3.provable, () => ForeignField.mul(x, x, f)); + let y2 = Provable.witness(Field3.provable, () => ForeignField.mul(y, y, f)); + + // assert (x - y) * (x + y) = x^2 - y^2 + let lhs = ForeignField.mul( + ForeignField.sub(x, y, f), + ForeignField.add(x, y, f), + f + ); + let rhs = ForeignField.sub(x2, y2, f); + Provable.assertEqual(Field3.provable, lhs, rhs); +} + function throwError(message: string): T { throw Error(message); } From ab198f564739924f733eb9cad701468b35e4703f Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 21 Nov 2023 23:32:17 +0100 Subject: [PATCH 0758/1215] handle constant case in Sum --- src/lib/gadgets/foreign-field.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 23835e7727..7242a60c1b 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -470,18 +470,27 @@ class Sum { return this; } - #finishOne() { - let result = this.#summands[0]; - this.#result = result; - return result; + #return(x: Field3) { + this.#result = x; + return x; + } + + isConstant() { + return this.#summands.every((x) => x.every((x) => x.isConstant())); } finish(f: bigint, isChained = false) { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.#finishOne(); + if (n === 0) return this.#return(this.#summands[0]); + // constant case + if (this.isConstant()) { + return this.#return(sum(this.#summands, signs, f)); + } + + // provable case let x = this.#summands.map(toVars); let result = x[0]; @@ -499,8 +508,14 @@ class Sum { assert(this.#result === undefined, 'sum already finished'); let signs = this.#ops; let n = signs.length; - if (n === 0) return this.#finishOne(); + if (n === 0) return this.#return(this.#summands[0]); + + // constant case + if (this.isConstant()) { + return this.#return(sum(this.#summands, signs, f)); + } + // provable case let x = this.#summands.map(toVars); // since the sum becomes a multiplication input, we need to constrain all limbs _individually_. From a76c8c85388947934c8a1f62731c6188973ee6eb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 18:10:37 +0100 Subject: [PATCH 0759/1215] another helper method on Field3 --- src/lib/gadgets/foreign-field.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 7242a60c1b..010311138a 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -59,7 +59,7 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { assert(x.length === sign.length + 1, 'inputs and operators match'); // constant case - if (x.every((x) => x.every((x) => x.isConstant()))) { + if (x.every(Field3.isConstant)) { let xBig = x.map(Field3.toBigint); let sum = sign.reduce((sum, s, i) => sum + s * xBig[i + 1], xBig[0]); return Field3.from(mod(sum, f)); @@ -121,7 +121,7 @@ function multiply(a: Field3, b: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (a.every((x) => x.isConstant()) && b.every((x) => x.isConstant())) { + if (Field3.isConstant(a) && Field3.isConstant(b)) { let ab = Field3.toBigint(a) * Field3.toBigint(b); return Field3.from(mod(ab, f)); } @@ -139,7 +139,7 @@ function inverse(x: Field3, f: bigint): Field3 { assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (x.every((x) => x.isConstant())) { + if (Field3.isConstant(x)) { let xInv = modInverse(Field3.toBigint(x), f); assert(xInv !== undefined, 'inverse exists'); return Field3.from(xInv); @@ -173,7 +173,7 @@ function divide( assert(f < 1n << 259n, 'Foreign modulus fits in 259 bits'); // constant case - if (x.every((x) => x.isConstant()) && y.every((x) => x.isConstant())) { + if (Field3.isConstant(x) && Field3.isConstant(y)) { let yInv = modInverse(Field3.toBigint(y), f); assert(yInv !== undefined, 'inverse exists'); return Field3.from(mod(Field3.toBigint(x) * yInv, f)); @@ -363,6 +363,13 @@ const Field3 = { return Tuple.map(xs, Field3.toBigint); }, + /** + * Check whether a 3-tuple of Fields is constant + */ + isConstant(x: Field3) { + return x.every((x) => x.isConstant()); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * From 188c477b247ece9e82d50cc5d9abe8d56adbac54 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 21 Nov 2023 23:39:34 +0100 Subject: [PATCH 0760/1215] constant case in assertMul --- src/lib/gadgets/foreign-field.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 010311138a..aa5d65f367 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -442,6 +442,20 @@ function assertMul( // x is chained into the ffmul gate let x0 = x.finishForMulInput(f, true); + + // constant case + if ( + Field3.isConstant(x0) && + Field3.isConstant(y0) && + Field3.isConstant(xy0) + ) { + let x_ = Field3.toBigint(x0); + let y_ = Field3.toBigint(y0); + let xy_ = Field3.toBigint(xy0); + assert(mod(x_ * y_, f) === xy_, 'incorrect multiplication result'); + return; + } + assertMulInternal(x0, y0, xy0, f); } @@ -483,7 +497,7 @@ class Sum { } isConstant() { - return this.#summands.every((x) => x.every((x) => x.isConstant())); + return this.#summands.every(Field3.isConstant); } finish(f: bigint, isChained = false) { From bedae7af81b042e49e30edcd35cf6ac3057fbb71 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 22 Nov 2023 00:59:26 +0100 Subject: [PATCH 0761/1215] add some tests for cs --- src/lib/gadgets/foreign-field.unit-test.ts | 49 ++++++++++++----- src/lib/testing/constraint-system.ts | 61 +++++++++++++++++++--- src/lib/util/types.ts | 3 +- 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 88753cdb6a..724c23d1fb 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -24,6 +24,7 @@ import { withoutGenerics, } from '../testing/constraint-system.js'; import { GateType } from '../../snarky.js'; +import { AnyTuple } from '../util/types.js'; const { ForeignField, Field3 } = Gadgets; @@ -193,7 +194,9 @@ let ffProgram = ZkProgram({ // tests for constraint system -let addChain = repeat(chainLength - 1, 'ForeignFieldAdd').concat('Zero'); +function addChain(length: number) { + return repeat(length - 1, 'ForeignFieldAdd').concat('Zero'); +} let mrc: GateType[] = ['RangeCheck0', 'RangeCheck0', 'RangeCheck1', 'Zero']; constraintSystem.fromZkProgram( @@ -201,8 +204,8 @@ constraintSystem.fromZkProgram( 'sumchain', ifNotAllConstant( and( - contains([addChain, mrc]), - withoutGenerics(equals([...addChain, ...mrc])) + contains([addChain(chainLength), mrc]), + withoutGenerics(equals([...addChain(chainLength), ...mrc])) ) ) ); @@ -227,7 +230,7 @@ constraintSystem.fromZkProgram(ffProgram, 'div', invLayout); // tests with proving -const runs = 3; +const runs = 2; await ffProgram.compile(); @@ -261,16 +264,6 @@ await equivalentAsync({ from: [f, f], to: f }, { runs })( 'prove div' ); -// helper - -function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { - let sum = xs[0]; - for (let i = 0; i < signs.length; i++) { - sum = signs[i] === 1n ? F.add(sum, xs[i + 1]) : F.sub(sum, xs[i + 1]); - } - return sum; -} - // assert mul example // (x - y) * (x + y) = x^2 - y^2 @@ -305,6 +298,34 @@ function assertMulExampleNaive( Provable.assertEqual(Field3.provable, lhs, rhs); } +let from2 = { from: [f, f] satisfies AnyTuple }; +let gates = constraintSystem.size(from2, (x, y) => + assertMulExample(x, y, F.modulus) +); +let gatesNaive = constraintSystem.size(from2, (x, y) => + assertMulExampleNaive(x, y, F.modulus) +); +assert(gates + 10 < gatesNaive, 'assertMul() saves at least 10 constraints'); + +let addChainedIntoMul: GateType[] = ['ForeignFieldAdd', ...mulChain]; + +constraintSystem( + 'assert mul', + from2, + (x, y) => assertMulExample(x, y, F.modulus), + contains([addChain(1), addChain(1), addChainedIntoMul, mrc, mrc]) +); + +// helper + +function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { + let sum = xs[0]; + for (let i = 0; i < signs.length; i++) { + sum = signs[i] === 1n ? F.add(sum, xs[i + 1]) : F.sub(sum, xs[i + 1]); + } + return sum; +} + function throwError(message: string): T { throw Error(message); } diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 55c0cabae0..5ff563be43 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -18,6 +18,7 @@ export { not, and, or, + satisfies, equals, contains, allConstant, @@ -182,6 +183,16 @@ function or(...tests: ConstraintSystemTest[]): ConstraintSystemTest { return { kind: 'or', tests, label: `or(${tests.map((t) => t.label)})` }; } +/** + * General test + */ +function satisfies( + label: string, + run: (cs: Gate[], inputs: TypeAndValue[]) => boolean +): ConstraintSystemTest { + return { run, label }; +} + /** * Test for precise equality of the constraint system with a given list of gates. */ @@ -263,14 +274,12 @@ function ifNotAllConstant(test: ConstraintSystemTest): ConstraintSystemTest { } /** - * Test whether all inputs are constant. + * Test whether constraint system is empty. */ -const isEmpty: ConstraintSystemTest = { - run(cs) { - return cs.length === 0; - }, - label: 'cs is empty', -}; +const isEmpty = satisfies( + 'constraint system is empty', + (cs) => cs.length === 0 +); /** * Modifies a test so that it runs on the constraint system with generic gates filtered out. @@ -300,6 +309,44 @@ const print: ConstraintSystemTest = { label: '', }; +// Do other useful things with constraint systems + +/** + * Get constraint system as a list of gates. + */ +constraintSystem.gates = function gates>>( + inputs: { from: Input }, + main: (...args: CsParams) => void +) { + let types = inputs.from.map(provable); + let { gates } = Provable.constraintSystem(() => { + let values = types.map((type) => + Provable.witness(type, (): unknown => { + throw Error('not needed'); + }) + ) as CsParams; + main(...values); + }); + return gates; +}; + +function map(transform: (gates: Gate[]) => T) { + return >>( + inputs: { from: Input }, + main: (...args: CsParams) => void + ) => transform(constraintSystem.gates(inputs, main)); +} + +/** + * Get size of constraint system. + */ +constraintSystem.size = map((gates) => gates.length); + +/** + * Print constraint system. + */ +constraintSystem.print = map(printGates); + function repeat(n: number, gates: GateType | GateType[]): readonly GateType[] { gates = Array.isArray(gates) ? gates : [gates]; return Array(n).fill(gates).flat(); diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index 201824ec48..f343a89b7e 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -1,8 +1,9 @@ import { assert } from '../errors.js'; -export { Tuple, TupleN }; +export { Tuple, TupleN, AnyTuple }; type Tuple = [T, ...T[]] | []; +type AnyTuple = Tuple; const Tuple = { map, B>( From 7ee955e68cf3a8357d1712c2770d1b4bee8a6984 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 22 Nov 2023 01:07:57 +0100 Subject: [PATCH 0762/1215] more cs test --- src/lib/gadgets/foreign-field.unit-test.ts | 14 +++++++++++++- src/lib/testing/constraint-system.ts | 7 +++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 724c23d1fb..cd5219f9b3 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -20,6 +20,7 @@ import { contains, equals, ifNotAllConstant, + not, repeat, withoutGenerics, } from '../testing/constraint-system.js'; @@ -313,11 +314,22 @@ constraintSystem( 'assert mul', from2, (x, y) => assertMulExample(x, y, F.modulus), - contains([addChain(1), addChain(1), addChainedIntoMul, mrc, mrc]) + and( + contains([addChain(1), addChain(1), addChainedIntoMul]), + // assertMul() doesn't use any range checks besides on internal values and the quotient + containsNTimes(2, mrc) + ) ); // helper +function containsNTimes(n: number, pattern: readonly GateType[]) { + return and( + contains(repeat(n, pattern)), + not(contains(repeat(n + 1, pattern))) + ); +} + function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { let sum = xs[0]; for (let i = 0; i < signs.length; i++) { diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 5ff563be43..07a672f06b 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -347,9 +347,12 @@ constraintSystem.size = map((gates) => gates.length); */ constraintSystem.print = map(printGates); -function repeat(n: number, gates: GateType | GateType[]): readonly GateType[] { +function repeat( + n: number, + gates: GateType | readonly GateType[] +): readonly GateType[] { gates = Array.isArray(gates) ? gates : [gates]; - return Array(n).fill(gates).flat(); + return Array(n).fill(gates).flat(); } function toGatess( From 2b8494667722c551a301556da04567957d540abb Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:22:01 +0530 Subject: [PATCH 0763/1215] :hammer: Updating state.ts to deprecate precondition apis As discussed in issue #1247 , I have replaced old api by deprecating them rather than removing. Question: Do we need to update this `assertStatePrecondition` too ? Please review and let know if anything else is required. --- src/lib/state.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/lib/state.ts b/src/lib/state.ts index 5be755d94d..cd5e744c5f 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -22,17 +22,17 @@ type State = { * Get the current on-chain state. * * Caution: If you use this method alone inside a smart contract, it does not prove that your contract uses the current on-chain state. - * To successfully prove that your contract uses the current on-chain state, you must add an additional `.assertEquals()` statement or use `.getAndAssertEquals()`: + * To successfully prove that your contract uses the current on-chain state, you must add an additional `.requireEquals()` statement or use `.getAndRequireEquals()`: * * ```ts * let x = this.x.get(); - * this.x.assertEquals(x); + * this.x.requireEquals(x); * ``` * * OR * * ```ts - * let x = this.x.getAndAssertEquals(); + * let x = this.x.getAndRequireEquals(); * ``` */ get(): A; @@ -40,6 +40,10 @@ type State = { * Get the current on-chain state and prove it really has to equal the on-chain state, * by adding a precondition which the verifying Mina node will check before accepting this transaction. */ + getAndRequireEquals(): A; + /** + * @deprecated use `this.state.getAndRequireEquals()` which is equivalent + */ getAndAssertEquals(): A; /** * Set the on-chain state to a new value. @@ -53,10 +57,18 @@ type State = { * Prove that the on-chain state has to equal the given state, * by adding a precondition which the verifying Mina node will check before accepting this transaction. */ + requireEquals(a: A): void; + /** + * @deprecated use `this.state.requireEquals()` which is equivalent + */ assertEquals(a: A): void; /** * **DANGER ZONE**: Override the error message that warns you when you use `.get()` without adding a precondition. */ + requireNothing(): void; + /** + * @deprecated use `this.state.requireNothing()` which is equivalent + */ assertNothing(): void; /** * Get the state from the raw list of field elements on a zkApp account, for example: @@ -203,6 +215,23 @@ function createState(): InternalStateType { }); }, + requireEquals(state: T) { + if (this._contract === undefined) + throw Error( + 'requireEquals can only be called when the State is assigned to a SmartContract @state.' + ); + let layout = getLayoutPosition(this._contract); + let stateAsFields = this._contract.stateType.toFields(state); + let accountUpdate = this._contract.instance.self; + stateAsFields.forEach((x, i) => { + AccountUpdate.assertEquals( + accountUpdate.body.preconditions.account.state[layout.offset + i], + x + ); + }); + this._contract.wasConstrained = true; + }, + assertEquals(state: T) { if (this._contract === undefined) throw Error( @@ -220,6 +249,14 @@ function createState(): InternalStateType { this._contract.wasConstrained = true; }, + requireNothing() { + if (this._contract === undefined) + throw Error( + 'requireNothing can only be called when the State is assigned to a SmartContract @state.' + ); + this._contract.wasConstrained = true; + }, + assertNothing() { if (this._contract === undefined) throw Error( @@ -294,6 +331,12 @@ function createState(): InternalStateType { return state; }, + getAndRequireEquals(){ + let state = this.get(); + this.requireEquals(state); + return state; + }, + getAndAssertEquals() { let state = this.get(); this.assertEquals(state); From 3d357c5fd7b00f87400f50f054e63da9d5576598 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 15 Nov 2023 17:24:01 +0100 Subject: [PATCH 0764/1215] tweak cs printing --- src/lib/testing/constraint-system.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 07a672f06b..d39c6f6991 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -495,9 +495,7 @@ function wiresToPretty(wires: Gate['wires'], row: number) { if (wire.row === row) { strWires.push(`${col}->${wire.col}`); } else { - let rowDelta = wire.row - row; - let rowStr = rowDelta > 0 ? `+${rowDelta}` : `${rowDelta}`; - strWires.push(`${col}->(${rowStr},${wire.col})`); + strWires.push(`${col}->(${wire.row},${wire.col})`); } } return strWires.join(', '); From 7db31879ff139259db51aa80a2724371f7c80ca3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 08:09:40 +0100 Subject: [PATCH 0765/1215] add reasoning for constraint reductions --- src/lib/gadgets/foreign-field.unit-test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index cd5219f9b3..8dc7f1eb54 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -88,8 +88,6 @@ for (let F of fields) { (x, y) => ForeignField.div(x, y, F.modulus), 'div' ); - - // assert mul equivalentProvable({ from: [f, f], to: unit })( (x, y) => assertMulExampleNaive(Field3.from(x), Field3.from(y), F.modulus), (x, y) => assertMulExample(x, y, F.modulus), @@ -306,7 +304,12 @@ let gates = constraintSystem.size(from2, (x, y) => let gatesNaive = constraintSystem.size(from2, (x, y) => assertMulExampleNaive(x, y, F.modulus) ); -assert(gates + 10 < gatesNaive, 'assertMul() saves at least 10 constraints'); +// the assertMul() version should save 11.5 rows: +// -2*1.5 rows by replacing input MRCs with low-limb ffadd +// -2*4 rows for avoiding the MRC on both mul() and sub() outputs +// -1 row for chaining one ffadd into ffmul +// +0.5 rows for having to combine the two lower result limbs before wiring to ffmul remainder +assert(gates + 11 <= gatesNaive, 'assertMul() saves at least 11 constraints'); let addChainedIntoMul: GateType[] = ['ForeignFieldAdd', ...mulChain]; From 12c227bd7c47d4861f7a8ca9085e0f8ca9c2041c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 21 Nov 2023 11:29:33 +0100 Subject: [PATCH 0766/1215] simplify low-level gadgets --- src/lib/gadgets/basic.ts | 23 +++++++++++++---------- src/lib/gadgets/common.ts | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index 4d16d0896e..d7247b1763 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -19,7 +19,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { let n = c.length; if (n === 0) { // (x - c1)*(x - c2) === 0 - assertBilinearZero(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + assertBilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); return; } // z = (x - c1)*(x - c2) @@ -31,7 +31,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); } else { // z*(x - c) === 0 - assertBilinearZero(z, xv, [1n, -c[i], 0n, 0n]); + assertBilinear(z, xv, [1n, -c[i], 0n, 0n]); } } } @@ -40,7 +40,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { /** * Compute bilinear function of x and y: - * z = a*x*y + b*x + c*y + d + * `z = a*x*y + b*x + c*y + d` */ function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { let z = existsOne(() => { @@ -57,17 +57,20 @@ function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { } /** - * Assert bilinear equation on x and y: - * a*x*y + b*x + c*y + d === 0 + * Assert bilinear equation on x, y and z: + * `a*x*y + b*x + c*y + d === z` + * + * The default for z is 0. */ -function assertBilinearZero( +function assertBilinear( x: VarField, y: VarField, - [a, b, c, d]: TupleN + [a, b, c, d]: TupleN, + z?: VarField ) { - // b*x + c*y + a*x*y + d === 0 + // b*x + c*y - z? + a*x*y + d === 0 Gates.generic( - { left: b, right: c, out: 0n, mul: a, const: d }, - { left: x, right: y, out: x } + { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, + { left: x, right: y, out: z === undefined ? x : z } ); } diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 1b52023ab1..e6e6c873cc 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -12,6 +12,7 @@ export { existsOne, toVars, toVar, + isVar, assert, bitSlice, witnessSlice, From 5f53f1c73056e8ec1336ddc523a37f7d05ca46b5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 08:50:47 +0100 Subject: [PATCH 0767/1215] comments --- src/lib/gadgets/basic.ts | 3 +++ src/lib/gadgets/foreign-field.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index d7247b1763..fff909cec3 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -1,3 +1,6 @@ +/** + * Basic gadgets that only use generic gates + */ import type { Field, VarField } from '../field.js'; import { existsOne, toVar } from './common.js'; import { Gates } from '../gates.js'; diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index aa5d65f367..67fba1dfd1 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -1,3 +1,6 @@ +/** + * Foreign field arithmetic gadgets. + */ import { inverse as modInverse, mod, From cc0220e3ccb2e0a94787275db51a7a536447135a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 08:56:06 +0100 Subject: [PATCH 0768/1215] changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a02ed147d..4128d60f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) -> No unreleased changes yet +# Added + +- `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From 46859464e3c1304da3e05a9f051902d9c3484930 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:26:59 +0100 Subject: [PATCH 0769/1215] add constant check --- src/lib/gadgets/bitwise.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index dc893b408b..757f99b7ca 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -210,7 +210,7 @@ function rotate64( if (field.isConstant()) { assert( - field.toBigInt() < 2n ** BigInt(MAX_BITS), + field.toBigInt() < 1n << BigInt(MAX_BITS), `rotation: expected field to be at most 64 bits, got ${field.toBigInt()}` ); return new Field(Fp.rot(field.toBigInt(), bits, direction)); @@ -226,6 +226,14 @@ function rotate32( ) { assert(bits <= 32 && bits > 0, 'bits must be between 0 and 32'); + if (field.isConstant()) { + assert( + field.toBigInt() < 1n << 32n, + `rotation: expected field to be at most 32 bits, got ${field.toBigInt()}` + ); + return new Field(Fp.rot(field.toBigInt(), bits, direction, 32)); + } + let { quotient: excess, remainder: shifted } = divMod32( field.mul(1n << BigInt(direction === 'left' ? bits : 32 - bits)) ); From 4013f14c9b45dbfa9151ed051149df7930e00caa Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:28:18 +0100 Subject: [PATCH 0770/1215] re-add range checks --- src/lib/gadgets/arithmetic.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 0e70d5a580..038428f10c 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -23,9 +23,8 @@ function divMod32(n: Field) { }); let [q, r] = qr; - // I think we can "skip" this here and do it in the caller - see rotate32 - // rangeCheck32(q); - // rangeCheck32(r); + rangeCheck32(q); + rangeCheck32(r); n.assertEquals(q.mul(1n << 32n).add(r)); From 4373bb31e7c1d9bc52925acace28073a761f93ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 09:39:20 +0100 Subject: [PATCH 0771/1215] comments, improve var naming --- src/lib/gadgets/foreign-field.ts | 43 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 67fba1dfd1..2ad29d76ad 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -540,7 +540,7 @@ class Sum { } // provable case - let x = this.#summands.map(toVars); + let xs = this.#summands.map(toVars); // since the sum becomes a multiplication input, we need to constrain all limbs _individually_. // sadly, ffadd only constrains the low and middle limb together. @@ -549,6 +549,8 @@ class Sum { let f_ = split(f); // compute witnesses for generic gates -- overflows and carries + // TODO: do this alongside the provable computation + // need As_prover.ref (= an auxiliary value) to pass the current full result x from one witness block to the next, to determine overflow let nFields = Provable.Array(Field, n); let [overflows, carries] = Provable.witness( provableTuple([nFields, nFields]), @@ -556,42 +558,43 @@ class Sum { let overflows: bigint[] = []; let carries: bigint[] = []; - let r = Field3.toBigint(x[0]); + let x = Field3.toBigint(xs[0]); for (let i = 0; i < n; i++) { // this duplicates some of the logic in singleAdd - let x_ = split(r); - let y_ = toBigint3(x[i + 1]); + let x0 = x & lMask; + let xi = toBigint3(xs[i + 1]); let sign = signs[i]; // figure out if there's overflow - r = r + sign * combine(y_); + x += sign * combine(xi); let overflow = 0n; - if (sign === 1n && r >= f) overflow = 1n; - if (sign === -1n && r < 0n) overflow = -1n; + if (sign === 1n && x >= f) overflow = 1n; + if (sign === -1n && x < 0n) overflow = -1n; if (f === 0n) overflow = 0n; overflows.push(overflow); + x -= overflow * f; // add with carry, only on the lowest limb - let r0 = x_[0] + sign * y_[0] - overflow * f_[0]; - carries.push(r0 >> l); + x0 = x0 + sign * xi[0] - overflow * f_[0]; + carries.push(x0 >> l); } return [overflows.map(Field.from), carries.map(Field.from)]; } ); // generic gates for low limbs - let x0 = x[0][0]; + let x0 = xs[0][0]; let x0s: Field[] = []; for (let i = 0; i < n; i++) { // constrain carry to 0, 1, or -1 let c = carries[i]; assertOneOf(c, [0n, 1n, -1n]); - // x0 <- x0 + s*y0 - o*f0 - c*2^l + // x0 <- x0 + s*xi0 - o*f0 - c*2^l x0 = toVar( x0 - .add(x[i + 1][0].mul(signs[i])) + .add(xs[i + 1][0].mul(signs[i])) .sub(overflows[i].mul(f_[0])) .sub(c.mul(1n << l)) ); @@ -599,18 +602,18 @@ class Sum { } // ffadd chain - let result = x[0]; + let x = xs[0]; for (let i = 0; i < n; i++) { - let r = singleAdd(result, x[i + 1], signs[i], f); + let { result, overflow } = singleAdd(x, xs[i + 1], signs[i], f); // wire low limb and overflow to previous values - r.result[0].assertEquals(x0s[i]); - r.overflow.assertEquals(overflows[i]); - result = r.result; + result[0].assertEquals(x0s[i]); + overflow.assertEquals(overflows[i]); + x = result; } - if (!isChained) Gates.zero(...result); + if (!isChained) Gates.zero(...x); - this.#result = result; - return result; + this.#result = x; + return x; } rangeCheck() { From bc9a20e47321503ed61daad06c2f1b9fd477c5b0 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:47:48 +0100 Subject: [PATCH 0772/1215] add doc comments --- src/lib/gadgets/gadgets.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index c332efdb64..e1aaffdc08 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -495,7 +495,37 @@ const Gadgets = { * **Note:** This interface does not contain any provable methods. */ Field3, + /** + * Division modulo 2^32. The operation decomposes a {@link Field} element into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. + * + * Asserts that both `remainder` and `quotient` are in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. + * + * @example + * ```ts + * let n = Field((1n << 32n) + 8n) + * let { remainder, quotient } = Gadgets.divMod32(n); + * // remainder = 8, quotient = 1 + * + * n.assertEquals(quotient.mul(1n << 32n).add(remainder)); + * ``` + */ divMod32, + + /** + * Addition modulo 2^32. The operation adds two {@link Field} elements and returns the result modulo 2^32. + * + * Asserts that the result is in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. + * + * It uses {@link Gadgets.divMod32} internally by adding the two {@link Field} elements and then decomposing the result into `remainder` and `quotient` and returning the `remainder`. + * + * @example + * ```ts + * let a = Field(8n); + * let b = Field(1n << 32n); + * + * Gadgets.addMod32(a, b).assertEquals(Field(8n)); + * ``` + * */ addMod32, }; From 14a77173cc3f4742a60221bcee7b6aaa836e36ab Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:48:29 +0100 Subject: [PATCH 0773/1215] rename r and q to remainder and quotient --- src/lib/gadgets/arithmetic.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 038428f10c..fb1e7f28fa 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -21,16 +21,16 @@ function divMod32(n: Field) { let r = nBigInt - q * (1n << 32n); return [new Field(q), new Field(r)]; }); - let [q, r] = qr; + let [quotient, remainder] = qr; - rangeCheck32(q); - rangeCheck32(r); + rangeCheck32(quotient); + rangeCheck32(remainder); - n.assertEquals(q.mul(1n << 32n).add(r)); + n.assertEquals(quotient.mul(1n << 32n).add(remainder)); return { - remainder: r, - quotient: q, + remainder, + quotient, }; } From 51d0bfded1661d67d81ab49b9eb7ae0b497bbb16 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:56:23 +0100 Subject: [PATCH 0774/1215] undo chain tests because not needed --- src/lib/gadgets/bitwise.unit-test.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index b019975909..8bc4105f3c 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -254,20 +254,5 @@ let isJustRotate = ifNotAllConstant( ); constraintSystem.fromZkProgram(Bitwise, 'rot64', isJustRotate); - -constraintSystem.fromZkProgram( - Bitwise, - 'rot32', - ifNotAllConstant( - contains([ - 'Generic', - 'Generic', - 'EndoMulScalar', - 'EndoMulScalar', - 'Generic', - ]) - ) -); - constraintSystem.fromZkProgram(Bitwise, 'leftShift', isJustRotate); constraintSystem.fromZkProgram(Bitwise, 'rightShift', isJustRotate); From ce34be8a20477fd653b7a229e8f9fbc2a085041d Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 09:58:14 +0100 Subject: [PATCH 0775/1215] Undo comment --- src/lib/gadgets/range-check.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index ec971d8c04..e9ccbb03f4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -24,7 +24,6 @@ function rangeCheck32(x: Field) { return; } - // can we make this more efficient? its 3 gates :/ let actual = x.rangeCheckHelper(32); actual.assertEquals(x); } From aedd9eb763995f127b6e1b86e5ddd3f63e9e44c1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 10:12:57 +0100 Subject: [PATCH 0776/1215] unconstrained provable --- src/lib/circuit_value.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 0f4106ebb9..616205c3c5 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -43,6 +43,7 @@ export { HashInput, InferJson, InferredProvable, + unconstrained, }; type ProvableExtension = { @@ -454,6 +455,14 @@ function Struct< return Struct_ as any; } +const unconstrained: Provable = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (t?: any) => [t], + fromFields: (_, [t]) => t, + check: () => {}, +}; + let primitives = new Set([Field, Bool, Scalar, Group]); function isPrimitive(obj: any) { for (let P of primitives) { From 152083bf49dc5068fc9ac5633c3ef007176f6647 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:30:46 +0100 Subject: [PATCH 0777/1215] better unconstrained provable --- src/lib/circuit_value.ts | 79 +++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 616205c3c5..2d8bfd5711 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { ProvablePure } from '../snarky.js'; +import { ProvablePure, Snarky } from '../snarky.js'; import { Field, Bool, Scalar, Group } from './core.js'; import { provable, @@ -15,6 +15,8 @@ import type { IsPure, } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; +import { assert } from './errors.js'; +import { inCheckedComputation } from './provable-context.js'; // external API export { @@ -43,7 +45,7 @@ export { HashInput, InferJson, InferredProvable, - unconstrained, + Unconstrained, }; type ProvableExtension = { @@ -455,13 +457,72 @@ function Struct< return Struct_ as any; } -const unconstrained: Provable = { - sizeInFields: () => 0, - toFields: () => [], - toAuxiliary: (t?: any) => [t], - fromFields: (_, [t]) => t, - check: () => {}, -}; +/** + * Container which holds an unconstrained value. This can be used to pass values + * between the out-of-circuit blocks in provable code. + * + * Invariants: + * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. + * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. + * (there is no way to create an empty `Unconstrained` in the prover) + */ +class Unconstrained { + private option: + | { isSome: true; value: T } + | { isSome: false; value: undefined }; + + private constructor(isSome: boolean, value?: T) { + this.option = { isSome, value: value as any }; + } + + /** + * Read an unconstrained value. + * + * Note: Can only be called outside provable code. + */ + get(): T { + if (inCheckedComputation() && !Snarky.run.inProverBlock()) + throw Error(`You cannot use Unconstrained.get() in provable code. + +The only place where you can read unconstrained values is in Provable.witness() +and Provable.asProver() blocks, which execute outside the proof. +`); + assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered + return this.option.value; + } + + /** + * Modify the unconstrained value. + */ + set(value: T) { + this.option = { isSome: true, value }; + } + + /** + * Create an `Unconstrained` with the given `value`. + */ + static from(value: T) { + return new Unconstrained(true, value); + } + + /** + * Create an `Unconstrained` from a witness computation. + */ + static witness(compute: () => T) { + return Provable.witness( + Unconstrained.provable, + () => new Unconstrained(true, compute()) + ); + } + + static provable: Provable> = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], + fromFields: (_, [t]) => t, + check: () => {}, + }; +} let primitives = new Set([Field, Bool, Scalar, Group]); function isPrimitive(obj: any) { From b49af92d7f82149334cd1f87eb64840448b445ee Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:31:04 +0100 Subject: [PATCH 0778/1215] allow passing .provable to methods --- src/lib/proof_system.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index c952b7aa6d..3d7f52c088 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -506,6 +506,9 @@ function sortMethodArguments( } else if (isAsFields(privateInput)) { allArgs.push({ type: 'witness', index: witnessArgs.length }); witnessArgs.push(privateInput); + } else if (isAsFields((privateInput as any)?.provable)) { + allArgs.push({ type: 'witness', index: witnessArgs.length }); + witnessArgs.push((privateInput as any).provable); } else if (isGeneric(privateInput)) { allArgs.push({ type: 'generic', index: genericArgs.length }); genericArgs.push(privateInput); From d26dab91a7bc07adf2831ff37ad208bcd542785c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:31:21 +0100 Subject: [PATCH 0779/1215] test unconstrained --- src/lib/circuit_value.unit-test.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index 6372c52a7f..79fbbafa9d 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -1,4 +1,4 @@ -import { provable, Struct } from './circuit_value.js'; +import { provable, Struct, Unconstrained } from './circuit_value.js'; import { UInt32 } from './int.js'; import { PrivateKey, PublicKey } from './signature.js'; import { expect } from 'expect'; @@ -96,6 +96,7 @@ class MyStructPure extends Struct({ class MyTuple extends Struct([PublicKey, String]) {} let targetString = 'some particular string'; +let targetBigint = 99n; let gotTargetString = false; // create a smart contract and pass auxiliary data to a method @@ -106,11 +107,22 @@ class MyContract extends SmartContract { // this works because MyStructPure only contains field elements @state(MyStructPure) x = State(); - @method myMethod(value: MyStruct, tuple: MyTuple, update: AccountUpdate) { + @method myMethod( + value: MyStruct, + tuple: MyTuple, + update: AccountUpdate, + unconstrained: Unconstrained + ) { // check if we can pass in string values if (value.other === targetString) gotTargetString = true; value.uint[0].assertEquals(UInt32.zero); + // cannot access unconstrained values in provable code + if (Provable.inCheckedComputation()) + expect(() => unconstrained.get()).toThrow( + 'You cannot use Unconstrained.get() in provable code.' + ); + Provable.asProver(() => { let err = 'wrong value in prover'; if (tuple[1] !== targetString) throw Error(err); @@ -119,6 +131,9 @@ class MyContract extends SmartContract { if (update.lazyAuthorization?.kind !== 'lazy-signature') throw Error(err); if (update.lazyAuthorization.privateKey?.toBase58() !== key.toBase58()) throw Error(err); + + // check if we can pass in unconstrained values + if (unconstrained.get() !== targetBigint) throw Error(err); }); } } @@ -141,7 +156,8 @@ let tx = await transaction(() => { uint: [UInt32.from(0), UInt32.from(10)], }, [address, targetString], - accountUpdate + accountUpdate, + Unconstrained.from(targetBigint) ); }); From 90941ac627a640d11314d0a473c5ca869bf2be8b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:32:02 +0100 Subject: [PATCH 0780/1215] merge provable and witness computation using unconstrained --- src/lib/gadgets/foreign-field.ts | 74 ++++++++++++++------------------ 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 2ad29d76ad..98c7ccae04 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -6,9 +6,9 @@ import { mod, } from '../../bindings/crypto/finite_field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; +import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { assertOneOf } from './basic.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; @@ -546,57 +546,47 @@ class Sum { // sadly, ffadd only constrains the low and middle limb together. // we could fix it with a RC just for the lower two limbs // but it's cheaper to add generic gates which handle the lowest limb separately, and avoids the unfilled MRC slot - let f_ = split(f); - - // compute witnesses for generic gates -- overflows and carries - // TODO: do this alongside the provable computation - // need As_prover.ref (= an auxiliary value) to pass the current full result x from one witness block to the next, to determine overflow - let nFields = Provable.Array(Field, n); - let [overflows, carries] = Provable.witness( - provableTuple([nFields, nFields]), - () => { - let overflows: bigint[] = []; - let carries: bigint[] = []; - - let x = Field3.toBigint(xs[0]); - - for (let i = 0; i < n; i++) { - // this duplicates some of the logic in singleAdd - let x0 = x & lMask; - let xi = toBigint3(xs[i + 1]); - let sign = signs[i]; - - // figure out if there's overflow - x += sign * combine(xi); - let overflow = 0n; - if (sign === 1n && x >= f) overflow = 1n; - if (sign === -1n && x < 0n) overflow = -1n; - if (f === 0n) overflow = 0n; - overflows.push(overflow); - x -= overflow * f; - - // add with carry, only on the lowest limb - x0 = x0 + sign * xi[0] - overflow * f_[0]; - carries.push(x0 >> l); - } - return [overflows.map(Field.from), carries.map(Field.from)]; - } - ); + let f0 = f & lMask; // generic gates for low limbs let x0 = xs[0][0]; let x0s: Field[] = []; + let overflows: Field[] = []; + let xRef = Unconstrained.witness(() => Field3.toBigint(xs[0])); + for (let i = 0; i < n; i++) { - // constrain carry to 0, 1, or -1 - let c = carries[i]; - assertOneOf(c, [0n, 1n, -1n]); + // compute carry and overflow + let [carry, overflow] = exists(2, () => { + // this duplicates some of the logic in singleAdd + let x = xRef.get(); + let x0 = x & lMask; + let xi = toBigint3(xs[i + 1]); + let sign = signs[i]; + + // figure out if there's overflow + x += sign * combine(xi); + let overflow = 0n; + if (sign === 1n && x >= f) overflow = 1n; + if (sign === -1n && x < 0n) overflow = -1n; + if (f === 0n) overflow = 0n; + xRef.set(x - overflow * f); + + // add with carry, only on the lowest limb + x0 = x0 + sign * xi[0] - overflow * f0; + let carry = x0 >> l; + return [carry, overflow]; + }); + overflows.push(overflow); + + // constrain carry + assertOneOf(carry, [0n, 1n, -1n]); // x0 <- x0 + s*xi0 - o*f0 - c*2^l x0 = toVar( x0 .add(xs[i + 1][0].mul(signs[i])) - .sub(overflows[i].mul(f_[0])) - .sub(c.mul(1n << l)) + .sub(overflow.mul(f0)) + .sub(carry.mul(1n << l)) ); x0s.push(x0); } From acd687712db2599994bbf233df05155eed764efe Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:15:08 +0530 Subject: [PATCH 0781/1215] :recycle: Refactored assert* to call require* --- src/lib/state.ts | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/lib/state.ts b/src/lib/state.ts index cd5e744c5f..dc848a1996 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -233,20 +233,7 @@ function createState(): InternalStateType { }, assertEquals(state: T) { - if (this._contract === undefined) - throw Error( - 'assertEquals can only be called when the State is assigned to a SmartContract @state.' - ); - let layout = getLayoutPosition(this._contract); - let stateAsFields = this._contract.stateType.toFields(state); - let accountUpdate = this._contract.instance.self; - stateAsFields.forEach((x, i) => { - AccountUpdate.assertEquals( - accountUpdate.body.preconditions.account.state[layout.offset + i], - x - ); - }); - this._contract.wasConstrained = true; + this.requireEquals(state); }, requireNothing() { @@ -258,11 +245,7 @@ function createState(): InternalStateType { }, assertNothing() { - if (this._contract === undefined) - throw Error( - 'assertNothing can only be called when the State is assigned to a SmartContract @state.' - ); - this._contract.wasConstrained = true; + this.requireNothing(); }, get() { @@ -331,16 +314,14 @@ function createState(): InternalStateType { return state; }, - getAndRequireEquals(){ + getAndRequireEquals() { let state = this.get(); this.requireEquals(state); return state; }, getAndAssertEquals() { - let state = this.get(); - this.assertEquals(state); - return state; + return this.getAndRequireEquals(); }, async fetch() { From 810480650a13b31332724e50a0b129ae9b34098d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:52:06 +0100 Subject: [PATCH 0782/1215] document and export `Unconstrained` --- CHANGELOG.md | 1 + src/index.ts | 1 + src/lib/circuit_value.ts | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4128d60f1c..cb2367d5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm # Added - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 +- `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) diff --git a/src/index.ts b/src/index.ts index 59d6f5eb5a..5c617fced6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export type { FlexibleProvable, FlexibleProvablePure, InferProvable, + Unconstrained, } from './lib/circuit_value.js'; export { CircuitValue, diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 2d8bfd5711..ad26b94699 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -465,6 +465,26 @@ function Struct< * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. * (there is no way to create an empty `Unconstrained` in the prover) + * + * @example + * ```ts + * let x = Unconstrained.from(0n); + * + * class MyContract extends SmartContract { + * `@method` myMethod(x: Unconstrained) { + * + * Provable.witness(Field, () => { + * // we can access and modify `x` here + * let newValue = x.get() + otherField.toBigInt(); + * x.set(newValue); + * + * // ... + * }); + * + * // throws an error! + * x.get(); + * } + * ``` */ class Unconstrained { private option: From 13c67350b3e62e25aa7a5b027971b32dfda34b4e Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:30:02 +0530 Subject: [PATCH 0783/1215] :memo: Updated Change log with the changes in pullrequest #1263 --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a02ed147d..60b4cf0a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) -> No unreleased changes yet +### Changed + +- Preconditioned asset* in state have been renamed to require* : + For example: + - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 + - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 + - `this.x.assertNothing()` is now `this.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1263 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From 4d78fc06f593bcd01d149886fc5327e24f968800 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 13:40:23 +0100 Subject: [PATCH 0784/1215] bump bindings --- src/bindings | 2 +- src/lib/gadgets/bitwise.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bindings b/src/bindings index 2d87533018..87996f7d27 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 2d875330187b95eb36f4602006e46ab2dcfe4cdd +Subproject commit 87996f7d27b37208d536349ab9449047964736f2 diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 757f99b7ca..b29f0293c7 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -213,7 +213,7 @@ function rotate64( field.toBigInt() < 1n << BigInt(MAX_BITS), `rotation: expected field to be at most 64 bits, got ${field.toBigInt()}` ); - return new Field(Fp.rot(field.toBigInt(), bits, direction)); + return new Field(Fp.rot(field.toBigInt(), BigInt(bits), direction)); } const [rotated] = rot64(field, bits, direction); return rotated; @@ -231,7 +231,7 @@ function rotate32( field.toBigInt() < 1n << 32n, `rotation: expected field to be at most 32 bits, got ${field.toBigInt()}` ); - return new Field(Fp.rot(field.toBigInt(), bits, direction, 32)); + return new Field(Fp.rot(field.toBigInt(), BigInt(bits), direction, 32n)); } let { quotient: excess, remainder: shifted } = divMod32( From d370e2974384f128484564668cc7288b754cf33e Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 13:44:45 +0100 Subject: [PATCH 0785/1215] fix return type opf witness --- src/lib/gadgets/arithmetic.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index fb1e7f28fa..12a1976688 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,3 +1,4 @@ +import { provableTuple } from '../circuit_value.js'; import { Field } from '../core.js'; import { Provable } from '../provable.js'; import { rangeCheck32 } from './range-check.js'; @@ -15,13 +16,15 @@ function divMod32(n: Field) { }; } - let qr = Provable.witness(Provable.Array(Field, 2), () => { - let nBigInt = n.toBigInt(); - let q = nBigInt / (1n << 32n); - let r = nBigInt - q * (1n << 32n); - return [new Field(q), new Field(r)]; - }); - let [quotient, remainder] = qr; + let [quotient, remainder] = Provable.witness( + provableTuple([Field, Field]), + () => { + let nBigInt = n.toBigInt(); + let q = nBigInt / (1n << 32n); + let r = nBigInt - q * (1n << 32n); + return [new Field(q), new Field(r)]; + } + ); rangeCheck32(quotient); rangeCheck32(remainder); From 08069bb81ebc729ef5c91143d73781023fc5eff0 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 14:34:19 +0100 Subject: [PATCH 0786/1215] limit input to 64bit, add tests --- src/lib/gadgets/arithmetic.ts | 6 +++ src/lib/gadgets/arithmetic.unit-test.ts | 68 +++++++++++++++++++++++++ src/lib/gadgets/bitwise.unit-test.ts | 8 +-- src/lib/gadgets/gadgets.ts | 8 ++- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 src/lib/gadgets/arithmetic.unit-test.ts diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 12a1976688..b2aee52d3d 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,5 +1,6 @@ import { provableTuple } from '../circuit_value.js'; import { Field } from '../core.js'; +import { assert } from '../errors.js'; import { Provable } from '../provable.js'; import { rangeCheck32 } from './range-check.js'; @@ -7,6 +8,11 @@ export { divMod32, addMod32 }; function divMod32(n: Field) { if (n.isConstant()) { + assert( + n.toBigInt() < 1n << 64n, + `n needs to fit in 64bit, but got ${n.toBigInt()}` + ); + let nBigInt = n.toBigInt(); let q = nBigInt / (1n << 32n); let r = nBigInt - q * (1n << 32n); diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts new file mode 100644 index 0000000000..996a9389b7 --- /dev/null +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -0,0 +1,68 @@ +import { ZkProgram } from '../proof_system.js'; +import { + array, + equivalentProvable as equivalent, + equivalentAsync, + field, +} from '../testing/equivalent.js'; +import { mod } from '../../bindings/crypto/finite_field.js'; +import { Field } from '../core.js'; +import { Gadgets } from './gadgets.js'; +import { Random } from '../testing/property.js'; +import { provable } from '../circuit_value.js'; +import { assert } from './common.js'; + +const maybeField = { + ...field, + rng: Random.map(Random.oneOf(Random.field, Random.field.invalid), (x) => + mod(x, Field.ORDER) + ), +}; + +let Arithmetic = ZkProgram({ + name: 'arithmetic', + publicOutput: provable({ + remainder: Field, + quotient: Field, + }), + methods: { + divMod32: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.divMod32(a); + }, + }, + }, +}); + +await Arithmetic.compile(); + +const divMod32Helper = (x: bigint) => { + let q = x / (1n << 32n); + let r = x - q * (1n << 32n); + return [r, q]; +}; + +equivalent({ from: [maybeField], to: array(field, 2) })( + (x) => { + assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); + return divMod32Helper(x); + }, + (x) => { + let { remainder, quotient } = Gadgets.divMod32(x); + return [remainder, quotient]; + } +); + +await equivalentAsync({ from: [maybeField], to: array(field, 2) }, { runs: 3 })( + (x) => { + assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); + return divMod32Helper(x); + }, + async (x) => { + let { + publicOutput: { quotient, remainder }, + } = await Arithmetic.divMod32(x); + return [remainder, quotient]; + } +); diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 8bc4105f3c..3b6183bfdd 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -109,7 +109,7 @@ await Bitwise.compile(); [2, 4, 8, 16, 32, 64].forEach((length) => { equivalent({ from: [uint(length)], to: field })( - (x) => Fp.rot(x, 12, 'left'), + (x) => Fp.rot(x, 12n, 'left'), (x) => Gadgets.rotate64(x, 12, 'left') ); equivalent({ from: [uint(length)], to: field })( @@ -124,7 +124,7 @@ await Bitwise.compile(); [2, 4, 8, 16, 32].forEach((length) => { equivalent({ from: [uint(length)], to: field })( - (x) => Fp.rot(x, 12, 'left', 32), + (x) => Fp.rot(x, 12n, 'left', 32n), (x) => Gadgets.rotate32(x, 12, 'left') ); }); @@ -177,7 +177,7 @@ await equivalentAsync( await equivalentAsync({ from: [field], to: field }, { runs: 3 })( (x) => { if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); - return Fp.rot(x, 12, 'left'); + return Fp.rot(x, 12n, 'left'); }, async (x) => { let proof = await Bitwise.rot64(x); @@ -188,7 +188,7 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( await equivalentAsync({ from: [field], to: field }, { runs: 3 })( (x) => { if (x >= 2n ** 32n) throw Error('Does not fit into 32 bits'); - return Fp.rot(x, 12, 'left', 32); + return Fp.rot(x, 12n, 'left', 32n); }, async (x) => { let proof = await Bitwise.rot32(x); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e1aaffdc08..cfcd166cad 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -496,7 +496,9 @@ const Gadgets = { */ Field3, /** - * Division modulo 2^32. The operation decomposes a {@link Field} element into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. + * Division modulo 2^32. The operation decomposes a {@link Field} element in the range [0, 2^64] into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. + * + * **Note:** The Gadget expects the input to be in the range [0, 2^64]. If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution. * * Asserts that both `remainder` and `quotient` are in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. * @@ -512,12 +514,14 @@ const Gadgets = { divMod32, /** - * Addition modulo 2^32. The operation adds two {@link Field} elements and returns the result modulo 2^32. + * Addition modulo 2^32. The operation adds two {@link Field} elements in the range [0, 2^64] and returns the result modulo 2^32. * * Asserts that the result is in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. * * It uses {@link Gadgets.divMod32} internally by adding the two {@link Field} elements and then decomposing the result into `remainder` and `quotient` and returning the `remainder`. * + * **Note:** The Gadget expects the input to be in the range [0, 2^64]. If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution. + * * @example * ```ts * let a = Field(8n); From 954607e4f0ab8f1dd535ae71971efadedea4a4c2 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 14:34:41 +0100 Subject: [PATCH 0787/1215] adjust error message --- src/lib/gadgets/arithmetic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index b2aee52d3d..351ddb0af1 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -10,7 +10,7 @@ function divMod32(n: Field) { if (n.isConstant()) { assert( n.toBigInt() < 1n << 64n, - `n needs to fit in 64bit, but got ${n.toBigInt()}` + `n needs to fit into 64 bit, but got ${n.toBigInt()}` ); let nBigInt = n.toBigInt(); From 6d4acd3120f14becf13a819bb46ff1bd17170f83 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 22 Nov 2023 15:57:03 +0100 Subject: [PATCH 0788/1215] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b4cf0a98..6201c24a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Preconditioned asset* in state have been renamed to require* : - For example: +- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertNothing()` is now `this.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1263 From e25d8962bc1903ebf54b38ab091547deb51e77c5 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 22 Nov 2023 16:28:33 +0100 Subject: [PATCH 0789/1215] add gadgets to regression tests --- tests/vk-regression/plain-constraint-system.ts | 9 ++++++++- tests/vk-regression/vk-regression.json | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index 9ee0e2737d..6a38b928fb 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -34,7 +34,14 @@ const GroupCS = constraintSystem('Group Primitive', { }); const BitwiseCS = constraintSystem('Bitwise Primitive', { - rot() { + rot32() { + let a = Provable.witness(Field, () => new Field(12)); + Gadgets.rotate32(a, 2, 'left'); + Gadgets.rotate32(a, 2, 'right'); + Gadgets.rotate32(a, 4, 'left'); + Gadgets.rotate32(a, 4, 'right'); + }, + rot64() { let a = Provable.witness(Field, () => new Field(12)); Gadgets.rangeCheck64(a); // `rotate()` doesn't do this Gadgets.rotate64(a, 2, 'left'); diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index f60540c9f8..d7f9381c21 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -168,7 +168,11 @@ "Bitwise Primitive": { "digest": "Bitwise Primitive", "methods": { - "rot": { + "rot32": { + "rows": 31, + "digest": "3e39e3f7bfdef1c546700c0294407fc2" + }, + "rot64": { "rows": 10, "digest": "c38703de755b10edf77bf24269089274" }, From 569e1c62945ff2729e4a1e38b964360fede142f0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 16:45:10 +0100 Subject: [PATCH 0790/1215] some API and comment tweaks --- src/lib/gadgets/ecdsa.unit-test.ts | 14 ++------ src/lib/gadgets/elliptic-curve.ts | 51 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index ff303b3cc8..d1cd6350c7 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -25,7 +25,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); -const tableConfig = { G: { windowSize: 4 }, P: { windowSize: 4 } }; +const config = { G: { windowSize: 4 }, P: { windowSize: 4 }, ia }; let program = ZkProgram({ name: 'ecdsa', @@ -37,10 +37,9 @@ let program = ZkProgram({ let P = Provable.witness(Point, () => publicKey); let R = EllipticCurve.multiScalarMul( Secp256k1, - ia, [signature.s, signature.r], [G, P], - [tableConfig.G, tableConfig.P] + [config.G, config.P] ); Provable.asProver(() => { console.log(Point.toBigint(R)); @@ -51,14 +50,7 @@ let program = ZkProgram({ privateInputs: [], method() { let signature0 = Provable.witness(Ecdsa.Signature, () => signature); - Ecdsa.verify( - Secp256k1, - ia, - signature0, - msgHash, - publicKey, - tableConfig - ); + Ecdsa.verify(Secp256k1, signature0, msgHash, publicKey, config); }, }, }, diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 9b9b7417ad..736e724bb8 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,5 +1,6 @@ import { FiniteField, + createField, inverse, mod, } from '../../bindings/crypto/finite_field.js'; @@ -129,6 +130,7 @@ function double(p1: Point, f: bigint) { let mBound = weakBound(m[2], f); let x3Bound = weakBound(x3[2], f); let y3Bound = weakBound(y3[2], f); + multiRangeCheck([mBound, x3Bound, y3Bound]); // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); @@ -148,21 +150,18 @@ function double(p1: Point, f: bigint) { let ySum = ForeignField.Sum(y1).add(y3); ForeignField.assertMul(deltaX1X3, m, ySum, f); - // bounds checks - multiRangeCheck([mBound, x3Bound, y3Bound]); - return { x: x3, y: y3 }; } function verifyEcdsa( Curve: CurveAffine, - ia: point, signature: EcdsaSignature, msgHash: Field3, publicKey: Point, - tables?: { + config?: { G?: { windowSize: number; multiples?: Point[] }; P?: { windowSize: number; multiples?: Point[] }; + ia?: point; } ) { // constant case @@ -191,14 +190,16 @@ function verifyEcdsa( let G = Point.from(Curve.one); let R = multiScalarMul( Curve, - ia, [u1, u2], [G, publicKey], - tables && [tables.G, tables.P] + config && [config.G, config.P], + config?.ia ); // this ^ already proves that R != 0 // reduce R.x modulo the curve order + // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: + // it's the callers responsibility to check that the signature is valid/unique in whatever way it makes sense for the application let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); Provable.assertEqual(Field3.provable, Rx, r); } @@ -220,18 +221,13 @@ function verifyEcdsa( */ function multiScalarMul( Curve: CurveAffine, - ia: point, scalars: Field3[], points: Point[], tableConfigs: ( - | { - // what we called c before - windowSize?: number; - // G, ..., (2^c-1)*G - multiples?: Point[]; - } + | { windowSize?: number; multiples?: Point[] } | undefined - )[] = [] + )[] = [], + ia?: point ): Point { let n = points.length; assert(scalars.length === n, 'Points and scalars lengths must match'); @@ -264,6 +260,8 @@ function multiScalarMul( slice(s, { maxBits: b, chunkSize: windowSizes[i] }) ); + // TODO: use Curve.Field + ia ??= initialAggregator(Curve, createField(Curve.modulus)); let sum = Point.from(ia); for (let i = b - 1; i >= 0; i--) { @@ -362,7 +360,7 @@ function getPointTable( function initialAggregator(Curve: CurveAffine, F: FiniteField) { // hash that identifies the curve let h = sha256.create(); - h.update('ecdsa'); + h.update('initial-aggregator'); h.update(bigIntToBytes(Curve.modulus)); h.update(bigIntToBytes(Curve.order)); h.update(bigIntToBytes(Curve.a)); @@ -386,12 +384,9 @@ function initialAggregator(Curve: CurveAffine, F: FiniteField) { } /** - * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `windowSize` + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `chunkSize` * - * TODO: atm this uses expensive boolean checks for the bits. - * For larger chunks, we should use more efficient range checks. - * - * Note: This serves as a range check for the input limbs + * This serves as a range check that the input is in [0, 2^maxBits) */ function slice( [x0, x1, x2]: Field3, @@ -402,7 +397,7 @@ function slice( // first limb let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); - if (maxBits <= l) return result0.chunks; + if (maxBits <= l_) return result0.chunks; maxBits -= l_; // second limb @@ -416,12 +411,16 @@ function slice( } /** - * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `windowSize` + * Provable method for slicing a field element into smaller bit chunks of length `chunkSize`. * - * TODO: atm this uses expensive boolean checks for the bits. - * For larger chunks, we should use more efficient range checks. + * This serves as a range check that the input is in [0, 2^maxBits) * - * Note: This serves as a range check that the input is in [0, 2^maxBits) + * If `chunkSize` does not divide `maxBits`, the last chunk will be smaller. + * We return the number of free bits in the last chunk, and optionally accept such a result from a previous call, + * so that this function can be used to slice up a bigint of multiple limbs into homogeneous chunks. + * + * TODO: atm this uses expensive boolean checks for each bit. + * For larger chunks, we should use more efficient range checks. */ function sliceField( x: Field, From a4b11378ca4e026fb076d35c3fdc22510c2b28bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:08:21 +0100 Subject: [PATCH 0791/1215] code moving --- src/lib/gadgets/foreign-field.unit-test.ts | 37 +++--------------- src/lib/gadgets/test-utils.ts | 44 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 src/lib/gadgets/test-utils.ts diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 8dc7f1eb54..371325cf61 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -1,7 +1,6 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { - ProvableSpec, array, equivalentAsync, equivalentProvable, @@ -26,36 +25,14 @@ import { } from '../testing/constraint-system.js'; import { GateType } from '../../snarky.js'; import { AnyTuple } from '../util/types.js'; +import { + foreignField, + throwError, + unreducedForeignField, +} from './test-utils.js'; const { ForeignField, Field3 } = Gadgets; -function foreignField(F: FiniteField): ProvableSpec { - return { - rng: Random.otherField(F), - there: Field3.from, - back: Field3.toBigint, - provable: Field3.provable, - }; -} - -// for testing with inputs > f -function unreducedForeignField( - maxBits: number, - F: FiniteField -): ProvableSpec { - return { - rng: Random.bignat(1n << BigInt(maxBits)), - there: Field3.from, - back: Field3.toBigint, - provable: Field3.provable, - assertEqual(x, y, message) { - // need weak equality here because, while ffadd works on bigints larger than the modulus, - // it can't fully reduce them - assert(F.equal(x, y), message); - }, - }; -} - let sign = fromRandom(Random.oneOf(1n as const, -1n as const)); let fields = [ @@ -340,7 +317,3 @@ function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { } return sum; } - -function throwError(message: string): T { - throw Error(message); -} diff --git a/src/lib/gadgets/test-utils.ts b/src/lib/gadgets/test-utils.ts new file mode 100644 index 0000000000..7f963d49ac --- /dev/null +++ b/src/lib/gadgets/test-utils.ts @@ -0,0 +1,44 @@ +import type { FiniteField } from '../../bindings/crypto/finite_field.js'; +import { ProvableSpec } from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { Gadgets } from './gadgets.js'; +import { assert } from './common.js'; + +export { foreignField, unreducedForeignField, throwError }; + +const { Field3 } = Gadgets; + +// test input specs + +function foreignField(F: FiniteField): ProvableSpec { + return { + rng: Random.otherField(F), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + }; +} + +// for testing with inputs > f +function unreducedForeignField( + maxBits: number, + F: FiniteField +): ProvableSpec { + return { + rng: Random.bignat(1n << BigInt(maxBits)), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + assertEqual(x, y, message) { + // need weak equality here because, while ffadd works on bigints larger than the modulus, + // it can't fully reduce them + assert(F.equal(x, y), message); + }, + }; +} + +// helper + +function throwError(message: string): T { + throw Error(message); +} From ea17473c5d0d6e9ff463636f0a33e8f356376ad0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:08:25 +0100 Subject: [PATCH 0792/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 67468138a2..3a40f7d2bb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 67468138a2b54b25276a7b475e3028be395d7a77 +Subproject commit 3a40f7d2bb360d33203cb03fda1177a87acfffed From 2898db3c0185cbb34247e7739d71c4495f00f970 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:08:42 +0100 Subject: [PATCH 0793/1215] adapt to bindings --- src/lib/gadgets/ecdsa.unit-test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index d1cd6350c7..532ffd21a8 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -1,14 +1,18 @@ import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { Ecdsa, EllipticCurve, Point } from './elliptic-curve.js'; import { Field3 } from './foreign-field.js'; -import { secp256k1Params } from '../../bindings/crypto/elliptic-curve-examples.js'; +import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { createField } from '../../bindings/crypto/finite_field.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; -const Secp256k1 = createCurveAffine(secp256k1Params); -const BaseField = createField(secp256k1Params.modulus); +// quick tests +// TODO + +// full end-to-end test with proving +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const BaseField = createField(Secp256k1.modulus); let publicKey = Point.from({ x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, From 9fd235337b1b7246561796ea3847caa31a34c2a4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:14:27 +0100 Subject: [PATCH 0794/1215] fields on curve --- src/lib/gadgets/elliptic-curve.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 736e724bb8..85a2e0b454 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -260,8 +260,7 @@ function multiScalarMul( slice(s, { maxBits: b, chunkSize: windowSizes[i] }) ); - // TODO: use Curve.Field - ia ??= initialAggregator(Curve, createField(Curve.modulus)); + ia ??= initialAggregator(Curve); let sum = Point.from(ia); for (let i = b - 1; i >= 0; i--) { @@ -357,7 +356,7 @@ function getPointTable( * It's important that this point has no known discrete logarithm so that nobody * can create an invalid proof of EC scaling. */ -function initialAggregator(Curve: CurveAffine, F: FiniteField) { +function initialAggregator(Curve: CurveAffine) { // hash that identifies the curve let h = sha256.create(); h.update('initial-aggregator'); @@ -369,6 +368,7 @@ function initialAggregator(Curve: CurveAffine, F: FiniteField) { // bytes represent a 256-bit number // use that as x coordinate + const F = Curve.Field; let x = F.mod(bytesToBigInt(bytes)); let y: bigint | undefined = undefined; From 3e9ee6b87c45cc93e86d02ff433b738bb6b9eefb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:22:30 +0100 Subject: [PATCH 0795/1215] ecdsa sign --- src/lib/gadgets/elliptic-curve.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 85a2e0b454..332d15ab1e 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,9 +1,4 @@ -import { - FiniteField, - createField, - inverse, - mod, -} from '../../bindings/crypto/finite_field.js'; +import { inverse, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { assert, exists } from './common.js'; @@ -324,6 +319,20 @@ function verifyEcdsaConstant( return mod(X.x, q) === r; } +/** + * Sign a message hash using ECDSA. + */ +function signEcdsa(Curve: CurveAffine, msgHash: bigint, privateKey: bigint) { + let { Scalar } = Curve; + let k = Scalar.random(); + let R = Curve.scale(Curve.one, k); + let r = Scalar.mod(R.x); + let kInv = Scalar.inverse(k); + assert(kInv !== undefined); + let s = Scalar.mul(kInv, Scalar.add(msgHash, Scalar.mul(r, privateKey))); + return { r, s }; +} + function getPointTable( Curve: CurveAffine, P: Point, @@ -536,6 +545,7 @@ const EcdsaSignature = { }; const Ecdsa = { + sign: signEcdsa, verify: verifyEcdsa, Signature: EcdsaSignature, }; From c501badd91468100089e53f35fb6e030e06778cd Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:22:40 +0100 Subject: [PATCH 0796/1215] adapt --- src/lib/gadgets/ecdsa.unit-test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 532ffd21a8..ad71411cd8 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -3,7 +3,6 @@ import { Ecdsa, EllipticCurve, Point } from './elliptic-curve.js'; import { Field3 } from './foreign-field.js'; import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; -import { createField } from '../../bindings/crypto/finite_field.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; @@ -12,7 +11,6 @@ import { assert } from './common.js'; // full end-to-end test with proving const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); -const BaseField = createField(Secp256k1.modulus); let publicKey = Point.from({ x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, @@ -28,7 +26,7 @@ let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn ); -const ia = EllipticCurve.initialAggregator(Secp256k1, BaseField); +const ia = EllipticCurve.initialAggregator(Secp256k1); const config = { G: { windowSize: 4 }, P: { windowSize: 4 }, ia }; let program = ZkProgram({ From 321f963689b1bcae7fddda8776ac22b9ce17b03b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 17:22:55 +0100 Subject: [PATCH 0797/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 3a40f7d2bb..916bc21458 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3a40f7d2bb360d33203cb03fda1177a87acfffed +Subproject commit 916bc21458bdb00a9277de755af15a1a5e111ba2 From 221057bb677593ce054bde6acedfe55e6dce7287 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Thu, 23 Nov 2023 00:38:53 +0530 Subject: [PATCH 0798/1215] :hammer: Update precondition apis to use require instead of assert as verb. Added test cases for verification --- src/lib/precondition.test.ts | 127 +++++++++++++++++++++++++++++++++++ src/lib/precondition.ts | 76 +++++++++++++++++---- 2 files changed, 190 insertions(+), 13 deletions(-) diff --git a/src/lib/precondition.test.ts b/src/lib/precondition.test.ts index d064ddc89f..07d0243d68 100644 --- a/src/lib/precondition.test.ts +++ b/src/lib/precondition.test.ts @@ -76,6 +76,21 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); + it('get + requireEquals should not throw', async () => { + let nonce = zkapp.account.nonce.get(); + let tx = await Mina.transaction(feePayer, () => { + zkapp.requireSignature(); + for (let precondition of implemented) { + let p = precondition().get(); + precondition().requireEquals(p as any); + } + AccountUpdate.attachToTransaction(zkapp.self); + }); + await tx.sign([feePayerKey, zkappKey]).send(); + // check that tx was applied, by checking nonce was incremented + expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); + }); + it('get + assertEquals should throw for unimplemented fields', async () => { for (let precondition of unimplemented) { await expect( @@ -88,6 +103,18 @@ describe('preconditions', () => { } }); + it('get + requireEquals should throw for unimplemented fields', async () => { + for (let precondition of unimplemented) { + await expect( + Mina.transaction(feePayer, () => { + let p = precondition(); + p.requireEquals(p.get() as any); + AccountUpdate.attachToTransaction(zkapp.self); + }) + ).rejects.toThrow(/not implemented/); + } + }); + it('get + assertBetween should not throw', async () => { let nonce = zkapp.account.nonce.get(); let tx = await Mina.transaction(feePayer, () => { @@ -103,6 +130,21 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); + it('get + requireBetween should not throw', async () => { + let nonce = zkapp.account.nonce.get(); + let tx = await Mina.transaction(feePayer, () => { + for (let precondition of implementedWithRange) { + let p: any = precondition().get(); + precondition().requireBetween(p.constructor.zero, p); + } + zkapp.requireSignature(); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await tx.sign([feePayerKey, zkappKey]).send(); + // check that tx was applied, by checking nonce was incremented + expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); + }); + it('satisfied currentSlot.assertBetween should not throw', async () => { let nonce = zkapp.account.nonce.get(); let tx = await Mina.transaction(feePayer, () => { @@ -117,6 +159,20 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); + it('satisfied currentSlot.requireBetween should not throw', async () => { + let nonce = zkapp.account.nonce.get(); + let tx = await Mina.transaction(feePayer, () => { + zkapp.currentSlot.requireBetween( + UInt32.from(0), + UInt32.from(UInt32.MAXINT()) + ); + zkapp.requireSignature(); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await tx.sign([feePayerKey, zkappKey]).send(); + expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); + }); + it('get + assertNothing should not throw', async () => { let nonce = zkapp.account.nonce.get(); let tx = await Mina.transaction(feePayer, () => { @@ -132,6 +188,21 @@ describe('preconditions', () => { expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); }); + it('get + requireNothing should not throw', async () => { + let nonce = zkapp.account.nonce.get(); + let tx = await Mina.transaction(feePayer, () => { + for (let precondition of implemented) { + precondition().get(); + precondition().requireNothing(); + } + zkapp.requireSignature(); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await tx.sign([feePayerKey, zkappKey]).send(); + // check that tx was applied, by checking nonce was incremented + expect(zkapp.account.nonce.get()).toEqual(nonce.add(1)); + }); + it('get + manual precondition should not throw', async () => { // we only test this for a couple of preconditions let nonce = zkapp.account.nonce.get(); @@ -172,6 +243,19 @@ describe('preconditions', () => { } }); + it('unsatisfied requireEquals should be rejected (numbers)', async () => { + for (let precondition of implementedNumber) { + await expect(async () => { + let tx = await Mina.transaction(feePayer, () => { + let p = precondition().get(); + precondition().requireEquals(p.add(1) as any); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await tx.sign([feePayerKey]).send(); + }).rejects.toThrow(/unsatisfied/); + } + }); + it('unsatisfied assertEquals should be rejected (booleans)', async () => { for (let precondition of implementedBool) { let tx = await Mina.transaction(feePayer, () => { @@ -185,6 +269,19 @@ describe('preconditions', () => { } }); + it('unsatisfied requireEquals should be rejected (booleans)', async () => { + for (let precondition of implementedBool) { + let tx = await Mina.transaction(feePayer, () => { + let p = precondition().get(); + precondition().requireEquals(p.not()); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await expect(tx.sign([feePayerKey]).send()).rejects.toThrow( + /unsatisfied/ + ); + } + }); + it('unsatisfied assertEquals should be rejected (public key)', async () => { let publicKey = PublicKey.from({ x: Field(-1), isOdd: Bool(false) }); let tx = await Mina.transaction(feePayer, () => { @@ -194,6 +291,15 @@ describe('preconditions', () => { await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); }); + it('unsatisfied requireEquals should be rejected (public key)', async () => { + let publicKey = PublicKey.from({ x: Field(-1), isOdd: Bool(false) }); + let tx = await Mina.transaction(feePayer, () => { + zkapp.account.delegate.requireEquals(publicKey); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); + }); + it('unsatisfied assertBetween should be rejected', async () => { for (let precondition of implementedWithRange) { let tx = await Mina.transaction(feePayer, () => { @@ -207,6 +313,19 @@ describe('preconditions', () => { } }); + it('unsatisfied requireBetween should be rejected', async () => { + for (let precondition of implementedWithRange) { + let tx = await Mina.transaction(feePayer, () => { + let p: any = precondition().get(); + precondition().requireBetween(p.add(20), p.add(30)); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await expect(tx.sign([feePayerKey]).send()).rejects.toThrow( + /unsatisfied/ + ); + } + }); + it('unsatisfied currentSlot.assertBetween should be rejected', async () => { let tx = await Mina.transaction(feePayer, () => { zkapp.currentSlot.assertBetween(UInt32.from(20), UInt32.from(30)); @@ -215,6 +334,14 @@ describe('preconditions', () => { await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); }); + it('unsatisfied currentSlot.requireBetween should be rejected', async () => { + let tx = await Mina.transaction(feePayer, () => { + zkapp.currentSlot.requireBetween(UInt32.from(20), UInt32.from(30)); + AccountUpdate.attachToTransaction(zkapp.self); + }); + await expect(tx.sign([feePayerKey]).send()).rejects.toThrow(/unsatisfied/); + }); + // TODO: is this a gotcha that should be addressed? // the test below fails, so it seems that nonce is applied successfully with a WRONG precondition.. // however, this is just because `zkapp.sign()` overwrites the nonce precondition with one that is satisfied diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 53e044f6ed..ac0bea3e79 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -49,11 +49,14 @@ function Network(accountUpdate: AccountUpdate): Network { let slot = network.globalSlotSinceGenesis.get(); return globalSlotToTimestamp(slot); }, - getAndAssertEquals() { - let slot = network.globalSlotSinceGenesis.getAndAssertEquals(); + getAndRequireEquals() { + let slot = network.globalSlotSinceGenesis.getAndRequireEquals(); return globalSlotToTimestamp(slot); }, - assertEquals(value: UInt64) { + getAndAssertEquals() { + return this.getAndRequireEquals(); + }, + requireEquals(value: UInt64) { let { genesisTimestamp, slotTime } = Mina.activeInstance.getNetworkConstants(); let slot = timestampToGlobalSlot( @@ -61,14 +64,26 @@ function Network(accountUpdate: AccountUpdate): Network { `Timestamp precondition unsatisfied: the timestamp can only equal numbers of the form ${genesisTimestamp} + k*${slotTime},\n` + `i.e., the genesis timestamp plus an integer number of slots.` ); - return network.globalSlotSinceGenesis.assertEquals(slot); + return network.globalSlotSinceGenesis.requireEquals(slot); }, - assertBetween(lower: UInt64, upper: UInt64) { + assertEquals(value: UInt64) { + return this.requireEquals(value); + }, + requireBetween(lower: UInt64, upper: UInt64) { let [slotLower, slotUpper] = timestampToGlobalSlotRange(lower, upper); - return network.globalSlotSinceGenesis.assertBetween(slotLower, slotUpper); + return network.globalSlotSinceGenesis.requireBetween( + slotLower, + slotUpper + ); + }, + assertBetween(lower: UInt64, upper: UInt64) { + return this.requireBetween(lower, upper); + }, + requireNothing() { + return network.globalSlotSinceGenesis.requireNothing(); }, assertNothing() { - return network.globalSlotSinceGenesis.assertNothing(); + return this.requireNothing(); }, }; return { ...network, timestamp }; @@ -118,7 +133,7 @@ function updateSubclass( function CurrentSlot(accountUpdate: AccountUpdate): CurrentSlot { let context = getPreconditionContextExn(accountUpdate); return { - assertBetween(lower: UInt32, upper: UInt32) { + requireBetween(lower: UInt32, upper: UInt32) { context.constrained.add('validWhile'); let property: RangeCondition = accountUpdate.body.preconditions.validWhile; @@ -126,6 +141,9 @@ function CurrentSlot(accountUpdate: AccountUpdate): CurrentSlot { property.value.lower = lower; property.value.upper = upper; }, + assertBetween(lower: UInt32, upper: UInt32) { + this.requireBetween(lower, upper); + }, }; } @@ -193,7 +211,7 @@ function preconditionSubClassWithRange< ) { return { ...preconditionSubclass(accountUpdate, longKey, fieldType as any, context), - assertBetween(lower: any, upper: any) { + requireBetween(lower: any, upper: any) { context.constrained.add(longKey); let property: RangeCondition = getPath( accountUpdate.body.preconditions, @@ -203,6 +221,9 @@ function preconditionSubClassWithRange< property.value.lower = lower; property.value.upper = upper; }, + assertBetween(lower: any, upper: any) { + this.requireBetween(lower, upper); + }, }; } @@ -232,12 +253,15 @@ function preconditionSubclass< fieldType )) as U; }, - getAndAssertEquals() { + getAndRequireEquals() { let value = obj.get(); - obj.assertEquals(value); + obj.requireEquals(value); return value; }, - assertEquals(value: U) { + getAndAssertEquals() { + return this.getAndRequireEquals(); + }, + requireEquals(value: U) { context.constrained.add(longKey); let property = getPath( accountUpdate.body.preconditions, @@ -255,9 +279,15 @@ function preconditionSubclass< setPath(accountUpdate.body.preconditions, longKey, value); } }, - assertNothing() { + assertEquals(value: U) { + this.requireEquals(value); + }, + requireNothing() { context.constrained.add(longKey); }, + assertNothing() { + this.requireNothing(); + }, }; return obj; } @@ -437,6 +467,10 @@ type Account = PreconditionClassType & Update; type CurrentSlotPrecondition = Preconditions['validWhile']; type CurrentSlot = { + requireBetween(lower: UInt32, upper: UInt32): void; + /** + * @deprecated use `requireBetween(lower: U, upper: U)` which is equivalent + */ assertBetween(lower: UInt32, upper: UInt32): void; }; @@ -452,11 +486,27 @@ type PreconditionBaseTypes = { type PreconditionSubclassType = { get(): U; + getAndRequireEquals(): U; + /** + * @deprecated use `getAndRequireEquals()` which is equivalent + */ getAndAssertEquals(): U; + requireEquals(value: U): void; + /** + * @deprecated use `requireEquals(value: U)` which is equivalent + */ assertEquals(value: U): void; + requireNothing(): void; + /** + * @deprecated use `requireNothing()` which is equivalent + */ assertNothing(): void; }; type PreconditionSubclassRangeType = PreconditionSubclassType & { + requireBetween(lower: U, upper: U): void; + /** + * @deprecated use `requireBetween(lower: U, upper: U)` which is equivalent + */ assertBetween(lower: U, upper: U): void; }; From bb9b6df175e9539b71b9cf627e5d94ece116111c Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Thu, 23 Nov 2023 00:48:19 +0530 Subject: [PATCH 0799/1215] :hammer: updated the deprecated message --- src/lib/precondition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index ac0bea3e79..25b10b9031 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -469,7 +469,7 @@ type CurrentSlotPrecondition = Preconditions['validWhile']; type CurrentSlot = { requireBetween(lower: UInt32, upper: UInt32): void; /** - * @deprecated use `requireBetween(lower: U, upper: U)` which is equivalent + * @deprecated use `requireBetween(lower: UInt32, upper: UInt32)` which is equivalent */ assertBetween(lower: UInt32, upper: UInt32): void; }; From c705ed577b96462b7def8bd558a79569894a47ca Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 20:38:11 +0100 Subject: [PATCH 0800/1215] fix test compilation --- src/lib/gadgets/elliptic-curve.unit-test.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index 5569f4dbc4..f820fc0a96 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -5,13 +5,10 @@ import { printGates } from '../testing/constraint-system.js'; import { assert } from './common.js'; import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { Fp } from '../../bindings/crypto/finite_field.js'; -import { - pallasParams, - secp256k1Params, -} from '../../bindings/crypto/elliptic-curve-examples.js'; +import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; -const Secp256k1 = createCurveAffine(secp256k1Params); -const Pallas = createCurveAffine(pallasParams); +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); let { add, double, initialAggregator } = EllipticCurve; @@ -42,6 +39,6 @@ console.log({ digest: csAdd.digest, rows: csAdd.rows }); printGates(csDouble.gates); console.log({ digest: csDouble.digest, rows: csDouble.rows }); -let point = initialAggregator(Pallas, Fp); +let point = initialAggregator(Pallas); console.log({ point }); assert(Pallas.isOnCurve(point)); From 9d480986e8485d59fd15a57bd61d2c18a02c571b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 21:38:57 +0100 Subject: [PATCH 0801/1215] map rng pf spec --- 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 ab1241b94b..1397d6d06f 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -25,6 +25,7 @@ export { unit, array, record, + map, fromRandom, }; export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; @@ -288,6 +289,13 @@ function record }>( }; } +function map( + { from, to }: { from: FromSpec; to: Spec }, + there: (t: T1) => S1 +): Spec { + return { ...to, rng: Random.map(from.rng, there) }; +} + function mapObject( t: { [k in K]: T }, map: (t: T, k: K) => S From 5780bfec0fc7625f515da7bf9a240f8778c1a4e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 21:39:36 +0100 Subject: [PATCH 0802/1215] wip quick ecdsa tests --- src/lib/gadgets/ecdsa.unit-test.ts | 76 ++++++++++++++++++++- src/lib/gadgets/elliptic-curve.ts | 4 ++ src/lib/gadgets/elliptic-curve.unit-test.ts | 1 - 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index ad71411cd8..e7fe3540f0 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -1,16 +1,86 @@ import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { Ecdsa, EllipticCurve, Point } from './elliptic-curve.js'; +import { + Ecdsa, + EllipticCurve, + Point, + verifyEcdsaConstant, +} from './elliptic-curve.js'; import { Field3 } from './foreign-field.js'; import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; +import { foreignField, throwError } from './test-utils.js'; +import { + equivalentProvable, + map, + oneOf, + record, + unit, +} from '../testing/equivalent.js'; // quick tests -// TODO +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); +const Vesta = createCurveAffine(CurveParams.Vesta); +let curves = [Secp256k1, Pallas, Vesta]; + +for (let Curve of curves) { + // prepare test inputs + let field = foreignField(Curve.Field); + let scalar = foreignField(Curve.Scalar); + + let pseudoPoint = record({ x: field, y: field }); + let point = map({ from: scalar, to: pseudoPoint }, (x) => + Curve.scale(Curve.one, x) + ); + + let pseudoSignature = record({ + signature: record({ r: scalar, s: scalar }), + msg: scalar, + publicKey: point, + }); + let signatureInputs = record({ privateKey: scalar, msg: scalar }); + let signature = map( + { from: signatureInputs, to: pseudoSignature }, + ({ privateKey, msg }) => { + let signature = Ecdsa.sign(Curve, msg, privateKey); + let publicKey = Curve.scale(Curve.one, privateKey); + return { signature, msg, publicKey }; + } + ); + + // positive test + equivalentProvable({ from: [signature], to: unit })( + () => {}, + ({ signature, publicKey, msg }) => { + Ecdsa.verify(Curve, signature, msg, publicKey); + }, + 'valid signature verifies' + ); + + // negative test + equivalentProvable({ from: [pseudoSignature], to: unit })( + () => throwError('invalid signature'), + ({ signature, publicKey, msg }) => { + Ecdsa.verify(Curve, signature, msg, publicKey); + }, + 'invalid signature fails' + ); + + // test against constant implementation, with both invalid and valid signatures + equivalentProvable({ from: [oneOf(signature, pseudoSignature)], to: unit })( + ({ signature, publicKey, msg }) => { + assert(verifyEcdsaConstant(Curve, signature, msg, publicKey), 'verifies'); + }, + ({ signature, publicKey, msg }) => { + Ecdsa.verify(Curve, signature, msg, publicKey); + }, + 'verify' + ); +} // full end-to-end test with proving -const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); let publicKey = Point.from({ x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 332d15ab1e..93a8527ab0 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -20,8 +20,12 @@ import { provable } from '../circuit_value.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; import { arrayGet } from './basic.js'; +// external API export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; +// internal API +export { verifyEcdsaConstant }; + const EllipticCurve = { add, double, diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index f820fc0a96..722ca3137a 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -4,7 +4,6 @@ import { EllipticCurve } from './elliptic-curve.js'; import { printGates } from '../testing/constraint-system.js'; import { assert } from './common.js'; import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { Fp } from '../../bindings/crypto/finite_field.js'; import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); From 4fee5294089b2cc738ac52bf12e150f3e36be65d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 10:04:07 +0100 Subject: [PATCH 0803/1215] finish unit test --- src/lib/gadgets/ecdsa.unit-test.ts | 34 ++++++++++++++---------------- src/lib/gadgets/test-utils.ts | 14 +++++++++++- src/lib/testing/equivalent.ts | 30 ++++++++++++++++---------- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index e7fe3540f0..ea380c171d 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -10,8 +10,9 @@ import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; -import { foreignField, throwError } from './test-utils.js'; +import { foreignField, throwError, uniformForeignField } from './test-utils.js'; import { + Second, equivalentProvable, map, oneOf, @@ -29,42 +30,41 @@ for (let Curve of curves) { // prepare test inputs let field = foreignField(Curve.Field); let scalar = foreignField(Curve.Scalar); - - let pseudoPoint = record({ x: field, y: field }); - let point = map({ from: scalar, to: pseudoPoint }, (x) => - Curve.scale(Curve.one, x) - ); + let privateKey = uniformForeignField(Curve.Scalar); let pseudoSignature = record({ signature: record({ r: scalar, s: scalar }), msg: scalar, - publicKey: point, + publicKey: record({ x: field, y: field }), }); - let signatureInputs = record({ privateKey: scalar, msg: scalar }); + + let signatureInputs = record({ privateKey, msg: scalar }); + let signature = map( { from: signatureInputs, to: pseudoSignature }, ({ privateKey, msg }) => { - let signature = Ecdsa.sign(Curve, msg, privateKey); let publicKey = Curve.scale(Curve.one, privateKey); + let signature = Ecdsa.sign(Curve, msg, privateKey); return { signature, msg, publicKey }; } ); + // provable method we want to test + const verify = (s: Second) => { + Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); + }; + // positive test equivalentProvable({ from: [signature], to: unit })( () => {}, - ({ signature, publicKey, msg }) => { - Ecdsa.verify(Curve, signature, msg, publicKey); - }, + verify, 'valid signature verifies' ); // negative test equivalentProvable({ from: [pseudoSignature], to: unit })( () => throwError('invalid signature'), - ({ signature, publicKey, msg }) => { - Ecdsa.verify(Curve, signature, msg, publicKey); - }, + verify, 'invalid signature fails' ); @@ -73,9 +73,7 @@ for (let Curve of curves) { ({ signature, publicKey, msg }) => { assert(verifyEcdsaConstant(Curve, signature, msg, publicKey), 'verifies'); }, - ({ signature, publicKey, msg }) => { - Ecdsa.verify(Curve, signature, msg, publicKey); - }, + verify, 'verify' ); } diff --git a/src/lib/gadgets/test-utils.ts b/src/lib/gadgets/test-utils.ts index 7f963d49ac..309dac6161 100644 --- a/src/lib/gadgets/test-utils.ts +++ b/src/lib/gadgets/test-utils.ts @@ -4,7 +4,7 @@ import { Random } from '../testing/random.js'; import { Gadgets } from './gadgets.js'; import { assert } from './common.js'; -export { foreignField, unreducedForeignField, throwError }; +export { foreignField, unreducedForeignField, uniformForeignField, throwError }; const { Field3 } = Gadgets; @@ -37,6 +37,18 @@ function unreducedForeignField( }; } +// for fields that must follow an unbiased distribution, like private keys +function uniformForeignField( + F: FiniteField +): ProvableSpec { + return { + rng: Random(F.random), + there: Field3.from, + back: Field3.toBigint, + provable: Field3.provable, + }; +} + // helper function throwError(message: string): T { diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 1397d6d06f..c02cf23afc 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -28,7 +28,15 @@ export { map, fromRandom, }; -export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; +export { + Spec, + ToSpec, + FromSpec, + SpecFromFunctions, + ProvableSpec, + First, + Second, +}; // a `Spec` tells us how to compare two functions @@ -109,8 +117,8 @@ function equivalent< Out extends ToSpec >({ from, to }: { from: In; to: Out }) { return function run( - f1: (...args: Params1) => Result1, - f2: (...args: Params2) => Result2, + f1: (...args: Params1) => First, + f2: (...args: Params2) => Second, label = 'expect equal results' ) { let generators = from.map((spec) => spec.rng); @@ -138,8 +146,8 @@ function equivalentAsync< Out extends ToSpec >({ from, to }: { from: In; to: Out }, { runs = 1 } = {}) { return async function run( - f1: (...args: Params1) => Promise> | Result1, - f2: (...args: Params2) => Promise> | Result2, + f1: (...args: Params1) => Promise> | First, + f2: (...args: Params2) => Promise> | Second, label = 'expect equal results' ) { let generators = from.map((spec) => spec.rng); @@ -178,8 +186,8 @@ function equivalentProvable< >({ from: fromRaw, to }: { from: In; to: Out }) { let fromUnions = fromRaw.map(toUnion); return function run( - f1: (...args: Params1) => Result1, - f2: (...args: Params2) => Result2, + f1: (...args: Params1) => First, + f2: (...args: Params2) => Second, label = 'expect equal results' ) { let generators = fromUnions.map((spec) => spec.rng); @@ -279,8 +287,8 @@ function array( function record }>( specs: Specs ): Spec< - { [k in keyof Specs]: Result1 }, - { [k in keyof Specs]: Result2 } + { [k in keyof Specs]: First }, + { [k in keyof Specs]: Second } > { return { rng: Random.record(mapObject(specs, (spec) => spec.rng)) as any, @@ -405,9 +413,9 @@ type Params2>> = { [k in keyof Ins]: Param2; }; -type Result1> = Out extends ToSpec +type First> = Out extends ToSpec ? Out1 : never; -type Result2> = Out extends ToSpec +type Second> = Out extends ToSpec ? Out2 : never; From b35bf20e778e3d38b5cefc9513eada760866b2cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 10:04:16 +0100 Subject: [PATCH 0804/1215] clean up constant ecdsa --- src/lib/gadgets/elliptic-curve.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 93a8527ab0..b1436a178c 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -301,26 +301,25 @@ function multiScalarMul( */ function verifyEcdsaConstant( Curve: CurveAffine, - { r, s }: { r: bigint; s: bigint }, + { r, s }: ecdsaSignature, msgHash: bigint, - publicKey: { x: bigint; y: bigint } + publicKey: point ) { - let q = Curve.order; - let QA = Curve.fromNonzero(publicKey); - if (!Curve.isOnCurve(QA)) return false; - if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; + let pk = Curve.fromNonzero(publicKey); + if (!Curve.isOnCurve(pk)) return false; + if (Curve.hasCofactor && !Curve.isInSubgroup(pk)) return false; if (r < 1n || r >= Curve.order) return false; if (s < 1n || s >= Curve.order) return false; - let sInv = inverse(s, q); - if (sInv === undefined) throw Error('impossible'); - let u1 = mod(msgHash * sInv, q); - let u2 = mod(r * sInv, q); + let sInv = Curve.Scalar.inverse(s); + assert(sInv !== undefined); + let u1 = Curve.Scalar.mul(msgHash, sInv); + let u2 = Curve.Scalar.mul(r, sInv); - let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); - if (Curve.equal(X, Curve.zero)) return false; + let R = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(pk, u2)); + if (Curve.equal(R, Curve.zero)) return false; - return mod(X.x, q) === r; + return Curve.Scalar.equal(R.x, r); } /** From 3369ae30c491a83fd0957c243f0087a638a3cddc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 10:12:41 +0100 Subject: [PATCH 0805/1215] comment --- src/lib/gadgets/elliptic-curve.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index b1436a178c..943f8c517d 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -336,6 +336,10 @@ function signEcdsa(Curve: CurveAffine, msgHash: bigint, privateKey: bigint) { return { r, s }; } +/** + * Given a point P, create the list of multiples [0, P, 2P, 3P, ..., (2^windowSize-1) * P]. + * This method is provable, but won't create any constraints given a constant point. + */ function getPointTable( Curve: CurveAffine, P: Point, From cf27df57dd246668f6d87e49867613d834191172 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 10:23:33 +0100 Subject: [PATCH 0806/1215] api cleanup --- src/lib/gadgets/ecdsa.unit-test.ts | 7 +++++-- src/lib/gadgets/elliptic-curve.ts | 31 +++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index ea380c171d..47a4f0228e 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -104,7 +104,7 @@ let program = ZkProgram({ privateInputs: [], method() { let G = Point.from(Secp256k1.one); - let P = Provable.witness(Point, () => publicKey); + let P = Provable.witness(Point.provable, () => publicKey); let R = EllipticCurve.multiScalarMul( Secp256k1, [signature.s, signature.r], @@ -119,7 +119,10 @@ let program = ZkProgram({ ecdsa: { privateInputs: [], method() { - let signature0 = Provable.witness(Ecdsa.Signature, () => signature); + let signature0 = Provable.witness( + Ecdsa.Signature.provable, + () => signature + ); Ecdsa.verify(Secp256k1, signature0, msgHash, publicKey, config); }, }, diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 943f8c517d..8d960b6dd7 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -50,7 +50,7 @@ function add(p1: Point, p2: Point, f: bigint) { let { x: x2, y: y2 } = p2; // constant case - if (Provable.isConstant(Point, p1) && Provable.isConstant(Point, p2)) { + if (Point.isConstant(p1) && Point.isConstant(p2)) { let p3 = affineAdd(Point.toBigint(p1), Point.toBigint(p2), f); return Point.from(p3); } @@ -101,7 +101,7 @@ function double(p1: Point, f: bigint) { let { x: x1, y: y1 } = p1; // constant case - if (Provable.isConstant(Point, p1)) { + if (Point.isConstant(p1)) { let p3 = affineDouble(Point.toBigint(p1), f); return Point.from(p3); } @@ -165,9 +165,9 @@ function verifyEcdsa( ) { // constant case if ( - Provable.isConstant(EcdsaSignature, signature) && + EcdsaSignature.isConstant(signature) && Field3.isConstant(msgHash) && - Provable.isConstant(Point, publicKey) + Point.isConstant(publicKey) ) { let isValid = verifyEcdsaConstant( Curve, @@ -233,10 +233,7 @@ function multiScalarMul( assertPositiveInteger(n, 'Expected at least 1 point and scalar'); // constant case - if ( - scalars.every(Field3.isConstant) && - points.every((P) => Provable.isConstant(Point, P)) - ) { + if (scalars.every(Field3.isConstant) && points.every(Point.isConstant)) { // TODO dedicated MSM let s = scalars.map(Field3.toBigint); let P = points.map(Point.toBigint); @@ -270,13 +267,15 @@ function multiScalarMul( // pick point to add based on the scalar chunk let sj = scalarChunks[j][i / windowSize]; let sjP = - windowSize === 1 ? points[j] : arrayGetGeneric(Point, tables[j], sj); + windowSize === 1 + ? points[j] + : arrayGetGeneric(Point.provable, tables[j], sj); // ec addition let added = add(sum, sjP, Curve.modulus); // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) - sum = Provable.if(sj.equals(0), Point, sum, added); + sum = Provable.if(sj.equals(0), Point.provable, sum, added); } } @@ -290,7 +289,7 @@ function multiScalarMul( // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); - Provable.equal(Point, sum, Point.from(iaFinal)).assertFalse(); + Provable.equal(Point.provable, sum, Point.from(iaFinal)).assertFalse(); sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); return sum; @@ -516,23 +515,27 @@ function arrayGetGeneric(type: Provable, array: T[], index: Field) { } const Point = { - ...provable({ x: Field3.provable, y: Field3.provable }), from({ x, y }: point): Point { return { x: Field3.from(x), y: Field3.from(y) }; }, toBigint({ x, y }: Point) { return { x: Field3.toBigint(x), y: Field3.toBigint(y), infinity: false }; }, + isConstant: (P: Point) => Provable.isConstant(Point.provable, P), + + provable: provable({ x: Field3.provable, y: Field3.provable }), }; const EcdsaSignature = { - ...provable({ r: Field3.provable, s: Field3.provable }), from({ r, s }: ecdsaSignature): EcdsaSignature { return { r: Field3.from(r), s: Field3.from(s) }; }, toBigint({ r, s }: EcdsaSignature): ecdsaSignature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, + isConstant: (S: EcdsaSignature) => + Provable.isConstant(EcdsaSignature.provable, S), + /** * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). @@ -549,6 +552,8 @@ const EcdsaSignature = { let s = BigInt(`0x${signature.slice(64)}`); return EcdsaSignature.from({ r, s }); }, + + provable: provable({ r: Field3.provable, s: Field3.provable }), }; const Ecdsa = { From bf6d2e96194c0fc10f6da5ca3f404551cafb6555 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 11:08:22 +0100 Subject: [PATCH 0807/1215] comments and make sure r,s are valid --- src/lib/gadgets/elliptic-curve.ts | 39 +++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 8d960b6dd7..016a625e8b 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -21,7 +21,7 @@ import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; import { arrayGet } from './basic.js'; // external API -export { EllipticCurve, Point, Ecdsa, EcdsaSignature }; +export { EllipticCurve, Point, Ecdsa }; // internal API export { verifyEcdsaConstant }; @@ -39,11 +39,13 @@ const EllipticCurve = { type Point = { x: Field3; y: Field3 }; type point = { x: bigint; y: bigint }; -/** - * ECDSA signature consisting of two curve scalars. - */ -type EcdsaSignature = { r: Field3; s: Field3 }; -type ecdsaSignature = { r: bigint; s: bigint }; +namespace Ecdsa { + /** + * ECDSA signature consisting of two curve scalars. + */ + export type Signature = { r: Field3; s: Field3 }; + export type signature = { r: bigint; s: bigint }; +} function add(p1: Point, p2: Point, f: bigint) { let { x: x1, y: y1 } = p1; @@ -154,7 +156,7 @@ function double(p1: Point, f: bigint) { function verifyEcdsa( Curve: CurveAffine, - signature: EcdsaSignature, + signature: Ecdsa.Signature, msgHash: Field3, publicKey: Point, config?: { @@ -180,9 +182,11 @@ function verifyEcdsa( } // provable case - // TODO should we check that the publicKey is a valid point? probably not + // note: usually we don't check validity of inputs, like that the public key is a valid curve point + // we make an exception for the two non-standard conditions s != 0 and r != 0, + // which are unusual to capture in types and could be considered part of the verification algorithm let { r, s } = signature; - let sInv = ForeignField.inv(s, Curve.order); + let sInv = ForeignField.inv(s, Curve.order); // proves s != 0 let u1 = ForeignField.mul(msgHash, sInv, Curve.order); let u2 = ForeignField.mul(r, sInv, Curve.order); @@ -194,7 +198,10 @@ function verifyEcdsa( config && [config.G, config.P], config?.ia ); - // this ^ already proves that R != 0 + // this ^ already proves that R != 0 (part of ECDSA verification) + // if b is not a square, then R != 0 already proves that r === R.x != 0, because R.y^2 = b has no solutions + // Otherwise we check the condition r != 0 explicitly (important, because r = 0 => u2 = 0 kills the contribution of the private key) + if (Curve.Field.isSquare(Curve.b)) ForeignField.inv(r, Curve.modulus); // reduce R.x modulo the curve order // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: @@ -300,7 +307,7 @@ function multiScalarMul( */ function verifyEcdsaConstant( Curve: CurveAffine, - { r, s }: ecdsaSignature, + { r, s }: Ecdsa.signature, msgHash: bigint, publicKey: point ) { @@ -514,6 +521,8 @@ function arrayGetGeneric(type: Provable, array: T[], index: Field) { return a; } +// type/conversion helpers + const Point = { from({ x, y }: point): Point { return { x: Field3.from(x), y: Field3.from(y) }; @@ -527,20 +536,20 @@ const Point = { }; const EcdsaSignature = { - from({ r, s }: ecdsaSignature): EcdsaSignature { + from({ r, s }: Ecdsa.signature): Ecdsa.Signature { return { r: Field3.from(r), s: Field3.from(s) }; }, - toBigint({ r, s }: EcdsaSignature): ecdsaSignature { + toBigint({ r, s }: Ecdsa.Signature): Ecdsa.signature { return { r: Field3.toBigint(r), s: Field3.toBigint(s) }; }, - isConstant: (S: EcdsaSignature) => + isConstant: (S: Ecdsa.Signature) => Provable.isConstant(EcdsaSignature.provable, S), /** * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). */ - fromHex(rawSignature: string): EcdsaSignature { + fromHex(rawSignature: string): Ecdsa.Signature { let prefix = rawSignature.slice(0, 2); let signature = rawSignature.slice(2, 130); if (prefix !== '0x' || signature.length < 128) { From 4bb7b1a3a50ce6de540bde0ad329c7fa26ab394b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 11:12:34 +0100 Subject: [PATCH 0808/1215] fix --- src/lib/gadgets/elliptic-curve.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 016a625e8b..12c5eb7226 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -183,9 +183,10 @@ function verifyEcdsa( // provable case // note: usually we don't check validity of inputs, like that the public key is a valid curve point - // we make an exception for the two non-standard conditions s != 0 and r != 0, + // we make an exception for the two non-standard conditions r != 0 and s != 0, // which are unusual to capture in types and could be considered part of the verification algorithm let { r, s } = signature; + ForeignField.inv(r, Curve.order); // proves r != 0 (important, because r = 0 => u2 = 0 kills the private key contribution) let sInv = ForeignField.inv(s, Curve.order); // proves s != 0 let u1 = ForeignField.mul(msgHash, sInv, Curve.order); let u2 = ForeignField.mul(r, sInv, Curve.order); @@ -199,9 +200,6 @@ function verifyEcdsa( config?.ia ); // this ^ already proves that R != 0 (part of ECDSA verification) - // if b is not a square, then R != 0 already proves that r === R.x != 0, because R.y^2 = b has no solutions - // Otherwise we check the condition r != 0 explicitly (important, because r = 0 => u2 = 0 kills the contribution of the private key) - if (Curve.Field.isSquare(Curve.b)) ForeignField.inv(r, Curve.modulus); // reduce R.x modulo the curve order // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: From 415f8ae84e450e43bb9083d75842a69cb022f22a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 11:33:36 +0100 Subject: [PATCH 0809/1215] sketch public API --- src/lib/gadgets/elliptic-curve.ts | 4 +-- src/lib/gadgets/gadgets.ts | 45 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 12c5eb7226..413c7fe7fc 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -159,11 +159,11 @@ function verifyEcdsa( signature: Ecdsa.Signature, msgHash: Field3, publicKey: Point, - config?: { + config: { G?: { windowSize: number; multiples?: Point[] }; P?: { windowSize: number; multiples?: Point[] }; ia?: point; - } + } = { G: { windowSize: 4 }, P: { windowSize: 4 } } ) { // constant case if ( diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index a35e94e566..d6670c3e91 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -9,6 +9,8 @@ import { import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; +import { Ecdsa, Point } from './elliptic-curve.js'; +import { CurveAffine } from '../../bindings/crypto/elliptic_curve.js'; export { Gadgets }; @@ -515,6 +517,49 @@ const Gadgets = { }, }, + /** + * TODO + */ + Ecdsa: { + /** + * TODO + * + * @example + * ```ts + * let Curve = Curves.Secp256k1; // TODO provide this somehow + * // TODO easy way to check that foreign field elements are valid + * let signature = { r, s }; + * // TODO need a way to check that publicKey is on curve + * let publicKey = { x, y }; + * + * Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); + * ``` + */ + verify( + Curve: CurveAffine, + signature: Ecdsa.Signature, + msgHash: Field3, + publicKey: Point + ) { + Ecdsa.verify(Curve, signature, msgHash, publicKey); + }, + + /** + * TODO + * + * should this be here, given that it's not a provable method? + * maybe assert that we are not running in provable context + */ + sign(Curve: CurveAffine, msgHash: bigint, privateKey: bigint) { + return Ecdsa.sign(Curve, msgHash, privateKey); + }, + + /** + * Non-provable helper methods for interacting with ECDSA signatures. + */ + Signature: Ecdsa.Signature, + }, + /** * Helper methods to interact with 3-limb vectors of Fields. * From a9e75e8ae0fef780169c55e531d87518e3cff0ea Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 23 Nov 2023 11:33:40 +0100 Subject: [PATCH 0810/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 916bc21458..12fdc5af74 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 916bc21458bdb00a9277de755af15a1a5e111ba2 +Subproject commit 12fdc5af742978ea32c3ce87696d43ee64738006 From b451ebe95cf0bef51d64d55b32b3ce9d14f4d822 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 09:28:24 +0100 Subject: [PATCH 0811/1215] add helper to apply all ff range checks --- src/lib/gadgets/foreign-field.ts | 29 +++++++++++++++++++++++-- src/lib/gadgets/gadgets.ts | 36 +++++++++++++++++++++++++++++++- src/lib/util/types.ts | 4 ++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 8f718cf250..d45a800331 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -9,8 +9,7 @@ import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Provable } from '../provable.js'; -import { Tuple } from '../util/types.js'; +import { Tuple, TupleN } from '../util/types.js'; import { assertOneOf } from './basic.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; import { @@ -52,6 +51,8 @@ const ForeignField = { inv: inverse, div: divide, assertMul, + + assertAlmostFieldElements, }; /** @@ -345,6 +346,30 @@ function weakBound(x: Field, f: bigint) { return x.add(lMask - (f >> l2)); } +/** + * Apply range checks and weak bounds checks to a list of Field3s. + * Optimal if the list length is a multiple of 3. + */ +function assertAlmostFieldElements(xs: Field3[], f: bigint) { + let bounds: Field[] = []; + + for (let x of xs) { + multiRangeCheck(x); + + bounds.push(weakBound(x[2], f)); + if (TupleN.hasLength(3, bounds)) { + multiRangeCheck(bounds); + bounds = []; + } + } + if (TupleN.hasLength(1, bounds)) { + multiRangeCheck([...bounds, Field.from(0n), Field.from(0n)]); + } + if (TupleN.hasLength(2, bounds)) { + multiRangeCheck([...bounds, Field.from(0n)]); + } +} + const Field3 = { /** * Turn a bigint into a 3-tuple of Fields diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index d6670c3e91..3cb2c9ce8e 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -515,6 +515,36 @@ const Gadgets = { Sum(x: Field3) { return ForeignField.Sum(x); }, + + /** + * Prove that each of the given {@link Field3} elements is "almost" reduced modulo f, + * i.e., satisfies the assumptions required by {@link ForeignField.mul} and other gadgets: + * - each limb is in the range [0, 2^88) + * - the most significant limb is less or equal than the modulus, x[2] <= f[2] + * + * **Note**: This method is most efficient when the number of input elements is a multiple of 3. + * + * @throws if any of the assumptions is violated. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); + * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * + * ForeignField.assertAlmostFieldElements([x, y, z], f); + * + * // now we can use x, y, z as inputs to foreign field multiplication + * let xy = ForeignField.mul(x, y, f); + * let xyz = ForeignField.mul(xy, z, f); + * + * // since xy is an input to another multiplication, we need to prove that it is almost reduced again! + * ForeignField.assertAlmostFieldElements([xy], f); // TODO: would be more efficient to batch this with 2 other elements + * ``` + */ + assertAlmostFieldElements(xs: Field3[], f: bigint) { + ForeignField.assertAlmostFieldElements(xs, f); + }, }, /** @@ -550,7 +580,11 @@ const Gadgets = { * should this be here, given that it's not a provable method? * maybe assert that we are not running in provable context */ - sign(Curve: CurveAffine, msgHash: bigint, privateKey: bigint) { + sign( + Curve: CurveAffine, + msgHash: bigint, + privateKey: bigint + ): Ecdsa.signature { return Ecdsa.sign(Curve, msgHash, privateKey); }, diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index f343a89b7e..600f5f705b 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -38,6 +38,10 @@ const TupleN = { ); return arr as any; }, + + hasLength(n: N, tuple: T[]): tuple is TupleN { + return tuple.length === n; + }, }; type TupleRec = R['length'] extends N From d89133073a5676027c4efbab2c52307a628782e5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 09:40:38 +0100 Subject: [PATCH 0812/1215] add more documentation --- src/lib/gadgets/gadgets.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 3cb2c9ce8e..75466c9dc1 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -420,9 +420,11 @@ const Gadgets = { * **Assumptions**: In addition to the assumption that input limbs are in the range [0, 2^88), as in all foreign field gadgets, * this assumes an additional bound on the inputs: `x * y < 2^264 * p`, where p is the native modulus. * We usually assert this bound by proving that `x[2] < f[2] + 1`, where `x[2]` is the most significant limb of x. - * To do this, use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. + * To do this, we use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. * The implication is that x and y are _almost_ reduced modulo f. * + * All of the above assumptions are checked by {@link ForeignField.assertAlmostFieldElements}. + * * **Warning**: This gadget does not add the extra bound check on the result. * So, to use the result in another foreign field multiplication, you have to add the bound check on it yourself, again. * @@ -434,14 +436,8 @@ const Gadgets = { * let x = Provable.witness(Field3.provable, () => Field3.from(f - 1n)); * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); * - * // range check x, y - * Gadgets.multiRangeCheck(x); - * Gadgets.multiRangeCheck(y); - * - * // prove additional bounds - * let x2Bound = x[2].add((1n << 88n) - 1n - (f >> 176n)); - * let y2Bound = y[2].add((1n << 88n) - 1n - (f >> 176n)); - * Gadgets.multiRangeCheck([x2Bound, y2Bound, Field(0n)]); + * // range check x, y and prove additional bounds x[2] <= f[2] + * ForeignField.assertAlmostFieldElements([x, y], f); * * // compute x * y mod f * let z = ForeignField.mul(x, y, f); @@ -497,7 +493,13 @@ const Gadgets = { * * @example * ```ts - * // we assume that x, y, z, a, b, c are range-checked, analogous to `ForeignField.mul()` + * // range-check x, y, z, a, b, c + * ForeignField.assertAlmostFieldElements([x, y, z], f); + * Gadgets.multiRangeCheck(a); + * Gadgets.multiRangeCheck(b); + * Gadgets.multiRangeCheck(c); + * + * // create lazy input sums * let xMinusY = ForeignField.Sum(x).sub(y); * let aPlusBPlusC = ForeignField.Sum(a).add(b).add(c); * From 697ddb37d1a56472a193368c58d6ede1e327df3b Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 09:40:57 +0100 Subject: [PATCH 0813/1215] use range check helper in ec gadgets --- src/lib/gadgets/elliptic-curve.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 413c7fe7fc..c237bcbd66 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -73,14 +73,7 @@ function add(p1: Point, p2: Point, f: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - - multiRangeCheck(m); - multiRangeCheck(x3); - multiRangeCheck(y3); - let mBound = weakBound(m[2], f); - let x3Bound = weakBound(x3[2], f); - let y3Bound = weakBound(y3[2], f); - multiRangeCheck([mBound, x3Bound, y3Bound]); + ForeignField.assertAlmostFieldElements([m, x3, y3], f); // (x1 - x2)*m = y1 - y2 let deltaX = ForeignField.Sum(x1).sub(x2); @@ -124,14 +117,7 @@ function double(p1: Point, f: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - - multiRangeCheck(m); - multiRangeCheck(x3); - multiRangeCheck(y3); - let mBound = weakBound(m[2], f); - let x3Bound = weakBound(x3[2], f); - let y3Bound = weakBound(y3[2], f); - multiRangeCheck([mBound, x3Bound, y3Bound]); + ForeignField.assertAlmostFieldElements([m, x3, y3], f); // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); From 86da89002cc5932477632338a5758e73abf07f2c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 10:45:07 +0100 Subject: [PATCH 0814/1215] add tests --- src/lib/gadgets/foreign-field.unit-test.ts | 29 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 371325cf61..2e9c4732f3 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -2,6 +2,7 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; import { exampleFields } from '../../bindings/crypto/finite-field-examples.js'; import { array, + equivalent, equivalentAsync, equivalentProvable, fromRandom, @@ -30,6 +31,7 @@ import { throwError, unreducedForeignField, } from './test-utils.js'; +import { l2 } from './range-check.js'; const { ForeignField, Field3 } = Gadgets; @@ -105,6 +107,11 @@ for (let F of fields) { 'div unreduced' ); + equivalent({ from: [big264], to: unit })( + (x) => assertWeakBound(x, F.modulus), + (x) => ForeignField.assertAlmostFieldElements([x], F.modulus) + ); + // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( (xs, signs) => sum(xs, signs, F), @@ -134,6 +141,7 @@ for (let F of fields) { let F = exampleFields.secp256k1; let f = foreignField(F); +let big264 = unreducedForeignField(264, F); let chainLength = 5; let signs = [1n, -1n, -1n, 1n] satisfies (-1n | 1n)[]; @@ -147,6 +155,13 @@ let ffProgram = ZkProgram({ return ForeignField.sum(xs, signs, F.modulus); }, }, + mulWithBoundsCheck: { + privateInputs: [Field3.provable, Field3.provable], + method(x, y) { + ForeignField.assertAlmostFieldElements([x, y], F.modulus); + return ForeignField.mul(x, y, F.modulus); + }, + }, mul: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { @@ -220,10 +235,14 @@ await equivalentAsync({ from: [array(f, chainLength)], to: f }, { runs })( 'prove chain' ); -await equivalentAsync({ from: [f, f], to: f }, { runs })( - F.mul, +await equivalentAsync({ from: [big264, big264], to: f }, { runs })( + (x, y) => { + assertWeakBound(x, F.modulus); + assertWeakBound(y, F.modulus); + return F.mul(x, y); + }, async (x, y) => { - let proof = await ffProgram.mul(x, y); + let proof = await ffProgram.mulWithBoundsCheck(x, y); assert(await ffProgram.verify(proof), 'verifies'); return proof.publicOutput; }, @@ -317,3 +336,7 @@ function sum(xs: bigint[], signs: (1n | -1n)[], F: FiniteField) { } return sum; } + +function assertWeakBound(x: bigint, f: bigint) { + assert(x >= 0n && x >> l2 <= f >> l2, 'weak bound'); +} From 18da02eda68a75671a6846d1d32eb211cc63e452 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 11:20:52 +0100 Subject: [PATCH 0815/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 87996f7d27..a1c177d3e8 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 87996f7d27b37208d536349ab9449047964736f2 +Subproject commit a1c177d3e8c2d8afcdd892b23fe0bd5d09b8a9f6 From cbe61315c6aa8c94ae2de5b92db29f551002d62c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 11:21:01 +0100 Subject: [PATCH 0816/1215] add forceRecompile option --- src/lib/proof_system.ts | 14 ++++++++++++-- src/lib/zkapp.ts | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index c952b7aa6d..312521374b 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -260,7 +260,10 @@ function ZkProgram< } ): { name: string; - compile: (options?: { cache: Cache }) => Promise<{ verificationKey: string }>; + compile: (options?: { + cache?: Cache; + forceRecompile?: boolean; + }) => Promise<{ verificationKey: string }>; verify: ( proof: Proof< InferProvableOrUndefined>, @@ -338,7 +341,10 @@ function ZkProgram< } | undefined; - async function compile({ cache = Cache.FileSystemDefault } = {}) { + async function compile({ + cache = Cache.FileSystemDefault, + forceRecompile = false, + } = {}) { let methodsMeta = methodIntfs.map((methodEntry, i) => analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) ); @@ -351,6 +357,7 @@ function ZkProgram< gates, proofSystemTag: selfTag, cache, + forceRecompile, overrideWrapDomain: config.overrideWrapDomain, }); compileOutput = { provers, verify }; @@ -603,6 +610,7 @@ async function compileProgram({ gates, proofSystemTag, cache, + forceRecompile, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -612,6 +620,7 @@ async function compileProgram({ gates: Gate[][]; proofSystemTag: { name: string }; cache: Cache; + forceRecompile: boolean; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => @@ -630,6 +639,7 @@ async function compileProgram({ let picklesCache: Pickles.Cache = [ 0, function read_(mlHeader) { + if (forceRecompile) return MlResult.unitError(); let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader); let result = readCache(cache, header, (bytes) => decodeProverKey(mlHeader, bytes) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 11f4978e15..a0df136baf 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -662,7 +662,10 @@ class SmartContract { * it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**, * up to several minutes if your circuit is large or your hardware is not optimal for these operations. */ - static async compile({ cache = Cache.FileSystemDefault } = {}) { + static async compile({ + cache = Cache.FileSystemDefault, + forceRecompile = false, + } = {}) { let methodIntfs = this._methods ?? []; let methods = methodIntfs.map(({ methodName }) => { return ( @@ -690,6 +693,7 @@ class SmartContract { gates, proofSystemTag: this, cache, + forceRecompile, }); let verificationKey = { data: verificationKey_.data, From dba93e73adee51b7e9a5704a714dd2855d6234d4 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Fri, 24 Nov 2023 14:11:44 +0200 Subject: [PATCH 0817/1215] Rename Mina's rampup branch to o1js-main. --- .github/actions/live-tests-shared/action.yml | 16 ++++++++-------- .github/workflows/build-action.yml | 16 ++++++++-------- .github/workflows/live-tests.yml | 12 ++++++------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index cc223da0e3..05d8970d97 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -1,11 +1,11 @@ -name: 'Shared steps for live testing jobs' -description: 'Shared steps for live testing jobs' +name: "Shared steps for live testing jobs" +description: "Shared steps for live testing jobs" inputs: mina-branch-name: - description: 'Mina branch name in use by service container' + description: "Mina branch name in use by service container" required: true runs: - using: 'composite' + using: "composite" steps: - name: Wait for Mina network readiness uses: o1-labs/wait-for-mina-network-action@v1 @@ -14,13 +14,13 @@ runs: max-attempts: 60 polling-interval-ms: 10000 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '20' + node-version: "20" - name: Build o1js and execute tests env: - TEST_TYPE: 'Live integration tests' - USE_CUSTOM_LOCAL_NETWORK: 'true' + TEST_TYPE: "Live integration tests" + USE_CUSTOM_LOCAL_NETWORK: "true" run: | git submodule update --init --recursive npm ci diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index f1675138de..3801f8ea78 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -27,9 +27,9 @@ jobs: ] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' - name: Build o1js and execute tests @@ -49,9 +49,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' - name: Install Node dependencies @@ -82,9 +82,9 @@ jobs: needs: [Build-And-Test-Server, Build-And-Test-Web] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' - name: Build o1js @@ -106,9 +106,9 @@ jobs: needs: [Build-And-Test-Server, Build-And-Test-Web] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' - name: Build mina-signer diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml index deb6ad2946..d5d00b7615 100644 --- a/.github/workflows/live-tests.yml +++ b/.github/workflows/live-tests.yml @@ -1,4 +1,4 @@ -name: Test o1js against real network +name: Test o1js against the real network on: push: branches: @@ -19,7 +19,7 @@ jobs: if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') services: mina-local-network: - image: o1labs/mina-local-network:rampup-latest-lightnet + image: o1labs/mina-local-network:o1js-main-latest-lightnet env: NETWORK_TYPE: 'single-node' PROOF_LEVEL: 'none' @@ -32,11 +32,11 @@ jobs: - /tmp:/root/logs steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use shared steps for live testing jobs uses: ./.github/actions/live-tests-shared with: - mina-branch-name: rampup + mina-branch-name: o1js-main berkeley-branch: timeout-minutes: 25 @@ -57,7 +57,7 @@ jobs: - /tmp:/root/logs steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use shared steps for live testing jobs uses: ./.github/actions/live-tests-shared with: @@ -82,7 +82,7 @@ jobs: - /tmp:/root/logs steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use shared steps for live testing jobs uses: ./.github/actions/live-tests-shared with: From 43f1efdfc797910b144d982fd5d997a8a0dbd706 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:53:05 +0530 Subject: [PATCH 0818/1215] :memo: Adding logs into ChangeLog file --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6201c24a15..dfd3e03aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertNothing()` is now `this.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1263 + - `this.account.x.assertBetween()` is now `this.account.x.requireBetween()` https://github.com/o1-labs/o1js/pull/1265 + - `this.account.x.assertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 + - `this.account.x.assertNothing()` is now `this.account.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1265 + - `this.currentSlot.assertBetween(x,y)` is now `this.currentSlot.requireBetween(x,y)` https://github.com/o1-labs/o1js/pull/1265 + - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From ea7729105fd5a2bebff400eabfdf5a58b78d349b Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 21:54:21 +0100 Subject: [PATCH 0819/1215] refactor sizeInBits/Bytes --- src/bindings | 2 +- src/lib/bool.ts | 4 +--- src/lib/field.ts | 20 ++++---------------- src/lib/gadgets/bitwise.ts | 8 ++++---- src/mina-signer/src/memo.ts | 4 +--- src/provable/field-bigint.ts | 4 +--- 6 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/bindings b/src/bindings index 87996f7d27..f00805ef39 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 87996f7d27b37208d536349ab9449047964736f2 +Subproject commit f00805ef39896acc504cfb6f6f6c46454cb7a9f4 diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 387a4908da..e9710a510e 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -314,9 +314,7 @@ class Bool { return BoolBinable.readBytes(bytes, offset); } - static sizeInBytes() { - return 1; - } + static sizeInBytes: 1; static check(x: Bool): void { Snarky.field.assertBoolean(x.value); diff --git a/src/lib/field.ts b/src/lib/field.ts index de52ecf621..f09ebd085a 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1253,26 +1253,14 @@ class Field { } /** - * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * - * As all {@link Field} elements have 32 bytes, this function returns 32. - * - * @return The size of a {@link Field} element - 32. + * The size of a {@link Field} element in bytes - 32. */ - static sizeInBytes() { - return Fp.sizeInBytes(); - } + static sizeInBytes = Fp.sizeInBytes; /** - * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * - * As all {@link Field} elements have 255 bits, this function returns 255. - * - * @return The size of a {@link Field} element in bits - 255. + * The size of a {@link Field} element in bits - 255. */ - static sizeInBits() { - return Fp.sizeInBits; - } + static sizeInBits = Fp.sizeInBits; } const FieldBinable = defineBinable({ diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 4a8a66f041..027d996488 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -20,8 +20,8 @@ function not(a: Field, length: number, checked: boolean = false) { // Check that length does not exceed maximum field size in bits assert( - length < Field.sizeInBits(), - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + length < Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` ); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table @@ -156,8 +156,8 @@ function and(a: Field, b: Field, length: number) { // check that length does not exceed maximum field size in bits assert( - length <= Field.sizeInBits(), - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + length <= Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` ); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table diff --git a/src/mina-signer/src/memo.ts b/src/mina-signer/src/memo.ts index 976c822ca6..8b3ae05f6f 100644 --- a/src/mina-signer/src/memo.ts +++ b/src/mina-signer/src/memo.ts @@ -62,9 +62,7 @@ const Memo = { hash, ...withBits(Binable, SIZE * 8), ...base58(Binable, versionBytes.userCommandMemo), - sizeInBytes() { - return SIZE; - }, + sizeInBytes: SIZE, emptyValue() { return Memo.fromString(''); }, diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index ea2d797c83..88e6afd5cd 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -64,9 +64,7 @@ const Bool = pseudoClass( checkBool(x); return x; }, - sizeInBytes() { - return 1; - }, + sizeInBytes: 1, fromField(x: Field) { checkBool(x); return x as 0n | 1n; From f5f656e47dc550dae8339c23f58c59006dd12b71 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 25 Nov 2023 00:32:53 +0100 Subject: [PATCH 0820/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f00805ef39..544c8a7306 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f00805ef39896acc504cfb6f6f6c46454cb7a9f4 +Subproject commit 544c8a730618c9b912037abd7703755ef8b4c84d From 77162a43b67219b7416bb9c96dfa1e2e740fccb4 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 25 Nov 2023 00:33:42 +0100 Subject: [PATCH 0821/1215] remove provable from mina-signer types --- src/lib/bool.ts | 4 ++++ src/lib/events.ts | 4 ++-- src/lib/field.ts | 4 ++++ src/lib/hash-generic.ts | 4 ++-- src/lib/testing/random.ts | 19 +++++++++++-------- src/provable/curve-bigint.ts | 3 ++- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index e9710a510e..15d0db2548 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -295,6 +295,10 @@ class Bool { return 1; } + static emptyValue() { + return new Bool(false); + } + static toInput(x: Bool): { packed: [Field, number][] } { return { packed: [[x.toField(), 1] as [Field, number]] }; } diff --git a/src/lib/events.ts b/src/lib/events.ts index 3e92a30389..04efe23185 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -1,8 +1,8 @@ import { prefixes } from '../bindings/crypto/constants.js'; import { prefixToField } from '../bindings/lib/binable.js'; import { - GenericField, GenericProvableExtended, + GenericSignableField, } from '../bindings/lib/generic.js'; export { createEvents, dataAsHash }; @@ -15,7 +15,7 @@ function createEvents({ Field, Poseidon, }: { - Field: GenericField; + Field: GenericSignableField; Poseidon: Poseidon; }) { type Event = Field[]; diff --git a/src/lib/field.ts b/src/lib/field.ts index f09ebd085a..fe117f65ab 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1148,6 +1148,10 @@ class Field { // ProvableExtended + static emptyValue() { + return new Field(0n); + } + /** * Serialize the {@link Field} to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. * diff --git a/src/lib/hash-generic.ts b/src/lib/hash-generic.ts index c5e240ae0a..c2907a3963 100644 --- a/src/lib/hash-generic.ts +++ b/src/lib/hash-generic.ts @@ -1,4 +1,4 @@ -import { GenericField } from '../bindings/lib/generic.js'; +import { GenericField, GenericSignableField } from '../bindings/lib/generic.js'; import { prefixToField } from '../bindings/lib/binable.js'; export { createHashHelpers, HashHelpers }; @@ -11,7 +11,7 @@ type Hash = { type HashHelpers = ReturnType>; function createHashHelpers( - Field: GenericField, + Field: GenericSignableField, Hash: Hash ) { function salt(prefix: string) { diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 0d9c457cf2..65f0c1d4dc 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -23,10 +23,10 @@ import { PublicKey, StateHash, } from '../../bindings/mina-transaction/transaction-leaves-bigint.js'; -import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; +import { genericLayoutFold } from '../../bindings/lib/from-layout-signable.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { - GenericProvable, + GenericSignable, primitiveTypeMap, } from '../../bindings/lib/generic.js'; import { Scalar, PrivateKey, Group } from '../../provable/curve-bigint.js'; @@ -35,7 +35,10 @@ import { randomBytes } from '../../bindings/crypto/random.js'; import { alphabet } from '../base58.js'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { Memo } from '../../mina-signer/src/memo.js'; -import { ProvableExtended } from '../../bindings/lib/provable-bigint.js'; +import { + ProvableExtended, + Signable, +} from '../../bindings/lib/provable-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; @@ -113,9 +116,9 @@ const zkappUri = map(string(nat(50)), ZkappUri.fromJSON); const PrimitiveMap = primitiveTypeMap(); type Types = typeof TypeMap & typeof customTypes & typeof PrimitiveMap; -type Provable = GenericProvable; +type Signable_ = GenericSignable; type Generators = { - [K in keyof Types]: Types[K] extends Provable ? Random : never; + [K in keyof Types]: Types[K] extends Signable_ ? Random : never; }; const Generators: Generators = { Field: field, @@ -138,7 +141,7 @@ const Generators: Generators = { string: base58(nat(50)), // TODO replace various strings, like signature, with parsed types number: nat(3), }; -let typeToBigintGenerator = new Map, Random>( +let typeToBigintGenerator = new Map, Random>( [TypeMap, PrimitiveMap, customTypes] .map(Object.entries) .flat() @@ -214,7 +217,7 @@ function withInvalidRandomString(rng: Random) { } type JsonGenerators = { - [K in keyof Types]: Types[K] extends ProvableExtended + [K in keyof Types]: Types[K] extends Signable ? Random : never; }; @@ -241,7 +244,7 @@ const JsonGenerators: JsonGenerators = { string: base58(nat(50)), number: nat(3), }; -let typeToJsonGenerator = new Map, Random>( +let typeToJsonGenerator = new Map, Random>( [TypeMap, PrimitiveMap, customTypes] .map(Object.entries) .flat() diff --git a/src/provable/curve-bigint.ts b/src/provable/curve-bigint.ts index 78e1e37a45..bdb11d9640 100644 --- a/src/provable/curve-bigint.ts +++ b/src/provable/curve-bigint.ts @@ -12,6 +12,7 @@ import { BinableBigint, ProvableBigint, provable, + signable, } from '../bindings/lib/provable-bigint.js'; import { HashInputLegacy } from './poseidon-bigint.js'; @@ -79,7 +80,7 @@ let BinablePublicKey = withVersionNumber( * A public key, represented by a non-zero point on the Pallas curve, in compressed form { x, isOdd } */ const PublicKey = { - ...provable({ x: Field, isOdd: Bool }), + ...signable({ x: Field, isOdd: Bool }), ...withBase58(BinablePublicKey, versionBytes.publicKey), toJSON(publicKey: PublicKey) { From fcaa8a4a8ea3d3558e21ccee802d2422b7a2dfa0 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:58:44 +0530 Subject: [PATCH 0822/1215] :recycle: updated and tested simple zkapp examples --- src/examples/simple_zkapp.ts | 12 ++++++------ src/examples/simple_zkapp.web.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index a58b2238cc..26f379770d 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -28,11 +28,11 @@ class SimpleZkapp extends SmartContract { } @method update(y: Field): Field { - this.account.provedState.assertEquals(Bool(true)); - this.network.timestamp.assertBetween(beforeGenesis, UInt64.MAXINT()); + this.account.provedState.requireEquals(Bool(true)); + this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); this.emitEvent('update', y); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); let newX = x.add(y); this.x.set(newX); return newX; @@ -43,7 +43,7 @@ class SimpleZkapp extends SmartContract { * @param caller the privileged account */ @method payout(caller: PrivateKey) { - this.account.provedState.assertEquals(Bool(true)); + this.account.provedState.requireEquals(Bool(true)); // check that caller is the privileged account let callerAddress = caller.toPublicKey(); @@ -51,10 +51,10 @@ class SimpleZkapp extends SmartContract { // assert that the caller account is new - this way, payout can only happen once let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.assertEquals(Bool(true)); + callerAccountUpdate.account.isNew.requireEquals(Bool(true)); // pay out half of the zkapp balance to the caller let balance = this.account.balance.get(); - this.account.balance.assertEquals(balance); + this.account.balance.requireEquals(balance); let halfBalance = balance.div(2); this.send({ to: callerAccountUpdate, amount: halfBalance }); diff --git a/src/examples/simple_zkapp.web.ts b/src/examples/simple_zkapp.web.ts index 60f5643b6d..9a10d1e070 100644 --- a/src/examples/simple_zkapp.web.ts +++ b/src/examples/simple_zkapp.web.ts @@ -27,11 +27,11 @@ class SimpleZkapp extends SmartContract { } @method update(y: Field): Field { - this.account.provedState.assertEquals(Bool(true)); - this.network.timestamp.assertBetween(beforeGenesis, UInt64.MAXINT()); + this.account.provedState.requireEquals(Bool(true)); + this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); this.emitEvent('update', y); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); let newX = x.add(y); this.x.set(newX); return newX; @@ -42,7 +42,7 @@ class SimpleZkapp extends SmartContract { * @param caller the privileged account */ @method payout(caller: PrivateKey) { - this.account.provedState.assertEquals(Bool(true)); + this.account.provedState.requireEquals(Bool(true)); // check that caller is the privileged account let callerAddress = caller.toPublicKey(); @@ -50,10 +50,10 @@ class SimpleZkapp extends SmartContract { // assert that the caller account is new - this way, payout can only happen once let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.assertEquals(Bool(true)); + callerAccountUpdate.account.isNew.requireEquals(Bool(true)); // pay out half of the zkapp balance to the caller let balance = this.account.balance.get(); - this.account.balance.assertEquals(balance); + this.account.balance.requireEquals(balance); let halfBalance = balance.div(2); this.send({ to: callerAccountUpdate, amount: halfBalance }); From 3780d54712f74907bf1e05191f558f5fb52ee327 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:02:12 +0530 Subject: [PATCH 0823/1215] :recycle: updated and tested nullifier example --- src/examples/nullifier.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examples/nullifier.ts b/src/examples/nullifier.ts index e90a7c50cf..8f3ea81001 100644 --- a/src/examples/nullifier.ts +++ b/src/examples/nullifier.ts @@ -18,8 +18,8 @@ class PayoutOnlyOnce extends SmartContract { @state(Field) nullifierMessage = State(); @method payout(nullifier: Nullifier) { - let nullifierRoot = this.nullifierRoot.getAndAssertEquals(); - let nullifierMessage = this.nullifierMessage.getAndAssertEquals(); + let nullifierRoot = this.nullifierRoot.getAndRequireEquals(); + let nullifierMessage = this.nullifierMessage.getAndRequireEquals(); // verify the nullifier nullifier.verify([nullifierMessage]); @@ -38,7 +38,7 @@ class PayoutOnlyOnce extends SmartContract { this.nullifierRoot.set(newRoot); // we pay out a reward - let balance = this.account.balance.getAndAssertEquals(); + let balance = this.account.balance.getAndRequireEquals(); let halfBalance = balance.div(2); // finally, we send the payout to the public key associated with the nullifier From 91222f9729b98631f2ea3af98cc9bc17597c3935 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:02:53 +0530 Subject: [PATCH 0824/1215] :recycle: updated and tested changes for dex examples --- src/examples/zkapps/dex/dex-with-actions.ts | 20 +++++++++---------- src/examples/zkapps/dex/dex.ts | 22 ++++++++++----------- src/examples/zkapps/dex/erc20.ts | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index 4e31bf5453..34353a2c4d 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -91,10 +91,10 @@ class Dex extends SmartContract { // get balances of X and Y token let dexX = AccountUpdate.create(this.address, tokenX.token.id); - let x = dexX.account.balance.getAndAssertEquals(); + let x = dexX.account.balance.getAndRequireEquals(); let dexY = AccountUpdate.create(this.address, tokenY.token.id); - let y = dexY.account.balance.getAndAssertEquals(); + let y = dexY.account.balance.getAndRequireEquals(); // // assert dy === [dx * y/x], or x === 0 let isXZero = x.equals(UInt64.zero); @@ -112,7 +112,7 @@ class Dex extends SmartContract { // update l supply let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.add(dl)); // emit event @@ -160,7 +160,7 @@ class Dex extends SmartContract { this.token.burn({ address: this.sender, amount: dl }); // TODO: preconditioning on the state here ruins concurrent interactions, // there should be another `finalize` DEX method which reduces actions & updates state - this.totalSupply.set(this.totalSupply.getAndAssertEquals().sub(dl)); + this.totalSupply.set(this.totalSupply.getAndRequireEquals().sub(dl)); // emit event this.typedEvents.emit('redeem-liquidity', { address: this.sender, dl }); @@ -171,8 +171,8 @@ class Dex extends SmartContract { * the current action state and token supply */ @method assertActionsAndSupply(actionState: Field, totalSupply: UInt64) { - this.account.actionState.assertEquals(actionState); - this.totalSupply.assertEquals(totalSupply); + this.account.actionState.requireEquals(actionState); + this.totalSupply.requireEquals(totalSupply); } /** @@ -236,7 +236,7 @@ class DexTokenHolder extends SmartContract { @method redeemLiquidityFinalize() { // get redeem actions let dex = new Dex(this.address); - let fromActionState = this.redeemActionState.getAndAssertEquals(); + let fromActionState = this.redeemActionState.getAndRequireEquals(); let actions = dex.reducer.getActions({ fromActionState }); // get total supply of liquidity tokens _before_ applying these actions @@ -251,7 +251,7 @@ class DexTokenHolder extends SmartContract { }); // get our token balance - let x = this.account.balance.getAndAssertEquals(); + let x = this.account.balance.getAndRequireEquals(); let redeemActionState = dex.reducer.forEach( actions, @@ -296,8 +296,8 @@ class DexTokenHolder extends SmartContract { // get balances of X and Y token let dexX = AccountUpdate.create(this.address, tokenX.token.id); - let x = dexX.account.balance.getAndAssertEquals(); - let y = this.account.balance.getAndAssertEquals(); + let x = dexX.account.balance.getAndRequireEquals(); + let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) tokenX.transfer(user, dexX, dx); diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index e391bdeb35..f9906fb96e 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -61,10 +61,10 @@ function createDex({ // TODO: this creates extra account updates. we need to reuse these by passing them to or returning them from transfer() // but for that, we need the @method argument generalization let dexXUpdate = AccountUpdate.create(this.address, tokenX.token.id); - let dexXBalance = dexXUpdate.account.balance.getAndAssertEquals(); + let dexXBalance = dexXUpdate.account.balance.getAndRequireEquals(); let dexYUpdate = AccountUpdate.create(this.address, tokenY.token.id); - let dexYBalance = dexYUpdate.account.balance.getAndAssertEquals(); + let dexYBalance = dexYUpdate.account.balance.getAndRequireEquals(); // // assert dy === [dx * y/x], or x === 0 let isXZero = dexXBalance.equals(UInt64.zero); @@ -103,7 +103,7 @@ function createDex({ // update l supply let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.add(dl)); return dl; } @@ -195,7 +195,7 @@ function createDex({ // this makes sure there is enough l to burn (user balance stays >= 0), so l stays >= 0, so l was >0 before this.token.burn({ address: user, amount: dl }); let l = this.totalSupply.get(); - this.totalSupply.assertEquals(l); + this.totalSupply.requireEquals(l); this.totalSupply.set(l.sub(dl)); return l; } @@ -227,7 +227,7 @@ function createDex({ // in return, we give dy back let y = this.account.balance.get(); - this.account.balance.assertEquals(y); + this.account.balance.requireEquals(y); // we can safely divide by l here because the Dex contract logic wouldn't allow burnLiquidity if not l>0 let dy = y.mul(dl).div(l); // just subtract the balance, user gets their part one level higher @@ -256,7 +256,7 @@ function createDex({ // in return for dl, we give back dx, the X token part let x = this.account.balance.get(); - this.account.balance.assertEquals(x); + this.account.balance.requireEquals(x); let dx = x.mul(dl).div(l); // just subtract the balance, user gets their part one level higher this.balance.subInPlace(dx); @@ -276,7 +276,7 @@ function createDex({ // get balances let x = tokenX.getBalance(this.address); let y = this.account.balance.get(); - this.account.balance.assertEquals(y); + this.account.balance.requireEquals(y); // send x from user to us (i.e., to the same address as this but with the other token) tokenX.transfer(user, this.address, dx); // compute and send dy @@ -300,7 +300,7 @@ function createDex({ let tokenX = new TokenContract(otherTokenAddress); let x = tokenX.getBalance(this.address); let y = this.account.balance.get(); - this.account.balance.assertEquals(y); + this.account.balance.requireEquals(y); tokenX.transfer(user, this.address, dx); // this formula has been changed - we just give the user an additional 15 token @@ -394,7 +394,7 @@ class TokenContract extends SmartContract { amount: UInt64.MAXINT(), }); // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account this.balance.subInPlace(Mina.accountCreationFee()); } @@ -410,7 +410,7 @@ class TokenContract extends SmartContract { amount: UInt64.from(10n ** 6n), }); // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account this.balance.subInPlace(Mina.accountCreationFee()); } @@ -477,7 +477,7 @@ class TokenContract extends SmartContract { @method getBalance(publicKey: PublicKey): UInt64 { let accountUpdate = AccountUpdate.create(publicKey, this.token.id); let balance = accountUpdate.account.balance.get(); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); return balance; diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index 94944d85f9..3d0b3a4a25 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -83,7 +83,7 @@ class TrivialCoin extends SmartContract implements Erc20 { amount: this.SUPPLY, }); // assert that the receiving account is new, so this can be only done once - receiver.account.isNew.assertEquals(Bool(true)); + receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account this.balance.subInPlace(Mina.accountCreationFee()); @@ -115,7 +115,7 @@ class TrivialCoin extends SmartContract implements Erc20 { balanceOf(owner: PublicKey): UInt64 { let account = Account(owner, this.token.id); let balance = account.balance.get(); - account.balance.assertEquals(balance); + account.balance.requireEquals(balance); return balance; } allowance(owner: PublicKey, spender: PublicKey): UInt64 { From c63a54aec30424a62fa397bd7ded95dce77570ca Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:04:26 +0530 Subject: [PATCH 0825/1215] :recycle: updated and tested changes for hello_world example --- src/examples/zkapps/hello_world/hello_world.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/examples/zkapps/hello_world/hello_world.ts b/src/examples/zkapps/hello_world/hello_world.ts index 8ba43875c0..4ccd77bdd9 100644 --- a/src/examples/zkapps/hello_world/hello_world.ts +++ b/src/examples/zkapps/hello_world/hello_world.ts @@ -1,11 +1,4 @@ -import { - Field, - PrivateKey, - SmartContract, - State, - method, - state, -} from 'o1js'; +import { Field, PrivateKey, SmartContract, State, method, state } from 'o1js'; export const adminPrivateKey = PrivateKey.random(); export const adminPublicKey = adminPrivateKey.toPublicKey(); @@ -21,12 +14,12 @@ export class HelloWorld extends SmartContract { @method update(squared: Field, admin: PrivateKey) { const x = this.x.get(); - this.x.assertNothing(); + this.x.requireNothing(); x.square().assertEquals(squared); this.x.set(squared); const adminPk = admin.toPublicKey(); - this.account.delegate.assertEquals(adminPk); + this.account.delegate.requireEquals(adminPk); } } From 03660b3fd0a75d2b66f130bfcbb5678d901d8972 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:05:59 +0530 Subject: [PATCH 0826/1215] :recycle: updated and tested changes for merkle tree example --- src/examples/zkapps/merkle_tree/merkle_zkapp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/merkle_tree/merkle_zkapp.ts b/src/examples/zkapps/merkle_tree/merkle_zkapp.ts index de1c42e995..3bcb1bfa22 100644 --- a/src/examples/zkapps/merkle_tree/merkle_zkapp.ts +++ b/src/examples/zkapps/merkle_tree/merkle_zkapp.ts @@ -75,7 +75,7 @@ class Leaderboard extends SmartContract { // we fetch the on-chain commitment let commitment = this.commitment.get(); - this.commitment.assertEquals(commitment); + this.commitment.requireEquals(commitment); // we check that the account is within the committed Merkle Tree path.calculateRoot(account.hash()).assertEquals(commitment); From bf1b9c45ee3e4498540398e9d1919556b7b53416 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:06:33 +0530 Subject: [PATCH 0827/1215] :recycle: updated and tested changes for reducer example --- src/examples/zkapps/reducer/reducer.ts | 4 ++-- src/examples/zkapps/reducer/reducer_composite.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/examples/zkapps/reducer/reducer.ts b/src/examples/zkapps/reducer/reducer.ts index 9b3d46dec7..a9bbbb063d 100644 --- a/src/examples/zkapps/reducer/reducer.ts +++ b/src/examples/zkapps/reducer/reducer.ts @@ -33,9 +33,9 @@ class CounterZkapp extends SmartContract { @method rollupIncrements() { // get previous counter & actions hash, assert that they're the same as on-chain values let counter = this.counter.get(); - this.counter.assertEquals(counter); + this.counter.requireEquals(counter); let actionState = this.actionState.get(); - this.actionState.assertEquals(actionState); + this.actionState.requireEquals(actionState); // compute the new counter and hash from pending actions let pendingActions = this.reducer.getActions({ diff --git a/src/examples/zkapps/reducer/reducer_composite.ts b/src/examples/zkapps/reducer/reducer_composite.ts index ce20fff86c..20495848fe 100644 --- a/src/examples/zkapps/reducer/reducer_composite.ts +++ b/src/examples/zkapps/reducer/reducer_composite.ts @@ -44,9 +44,9 @@ class CounterZkapp extends SmartContract { @method rollupIncrements() { // get previous counter & actions hash, assert that they're the same as on-chain values let counter = this.counter.get(); - this.counter.assertEquals(counter); + this.counter.requireEquals(counter); let actionState = this.actionState.get(); - this.actionState.assertEquals(actionState); + this.actionState.requireEquals(actionState); // compute the new counter and hash from pending actions let pendingActions = this.reducer.getActions({ From cea01c563d196a50b30e19bdcef2cd438f4f2826 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:07:47 +0530 Subject: [PATCH 0828/1215] :recycle: updated and tested changes for sudoku example --- src/examples/zkapps/sudoku/sudoku.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts index 003b84c3f2..5175d011a1 100644 --- a/src/examples/zkapps/sudoku/sudoku.ts +++ b/src/examples/zkapps/sudoku/sudoku.ts @@ -98,7 +98,7 @@ class SudokuZkApp extends SmartContract { // finally, we check that the sudoku is the one that was originally deployed let sudokuHash = this.sudokuHash.get(); // get the hash from the blockchain - this.sudokuHash.assertEquals(sudokuHash); // precondition that links this.sudokuHash.get() to the actual on-chain state + this.sudokuHash.requireEquals(sudokuHash); // precondition that links this.sudokuHash.get() to the actual on-chain state sudokuInstance .hash() .assertEquals(sudokuHash, 'sudoku matches the one committed on-chain'); From 8c3e2bbe56ecb948fede81a25fd4903f99552dce Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:08:15 +0530 Subject: [PATCH 0829/1215] :recycle: updated and tested changes for voting example --- src/examples/zkapps/voting/membership.ts | 10 +++++----- src/examples/zkapps/voting/voting.ts | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/examples/zkapps/voting/membership.ts b/src/examples/zkapps/voting/membership.ts index 45a5705e3d..9853b70664 100644 --- a/src/examples/zkapps/voting/membership.ts +++ b/src/examples/zkapps/voting/membership.ts @@ -94,7 +94,7 @@ export class Membership_ extends SmartContract { let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -110,7 +110,7 @@ export class Membership_ extends SmartContract { ); let accumulatedMembers = this.accumulatedMembers.get(); - this.accumulatedMembers.assertEquals(accumulatedMembers); + this.accumulatedMembers.requireEquals(accumulatedMembers); // checking if the member already exists within the accumulator let { state: exists } = this.reducer.reduce( @@ -148,7 +148,7 @@ export class Membership_ extends SmartContract { // Preconditions: Item exists in committed storage let committedMembers = this.committedMembers.get(); - this.committedMembers.assertEquals(committedMembers); + this.committedMembers.requireEquals(committedMembers); return member.witness .calculateRootSlow(member.getHash()) @@ -162,10 +162,10 @@ export class Membership_ extends SmartContract { // Commit to the items accumulated so far. This is a periodic update let accumulatedMembers = this.accumulatedMembers.get(); - this.accumulatedMembers.assertEquals(accumulatedMembers); + this.accumulatedMembers.requireEquals(accumulatedMembers); let committedMembers = this.committedMembers.get(); - this.committedMembers.assertEquals(committedMembers); + this.committedMembers.requireEquals(committedMembers); let pendingActions = this.reducer.getActions({ fromActionState: accumulatedMembers, diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index c16f118a7f..2cf12f1666 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -116,7 +116,7 @@ export class Voting_ extends SmartContract { @method voterRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -133,7 +133,7 @@ export class Voting_ extends SmartContract { let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -165,7 +165,7 @@ export class Voting_ extends SmartContract { @method candidateRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -182,7 +182,7 @@ export class Voting_ extends SmartContract { // this snippet pulls the account data of an address from the network let accountUpdate = AccountUpdate.create(member.publicKey); - accountUpdate.account.balance.assertEquals( + accountUpdate.account.balance.requireEquals( accountUpdate.account.balance.get() ); @@ -229,7 +229,7 @@ export class Voting_ extends SmartContract { @method vote(candidate: Member, voter: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); - this.network.globalSlotSinceGenesis.assertBetween( + this.network.globalSlotSinceGenesis.requireBetween( currentSlot, currentSlot.add(10) ); @@ -266,10 +266,10 @@ export class Voting_ extends SmartContract { @method countVotes() { let accumulatedVotes = this.accumulatedVotes.get(); - this.accumulatedVotes.assertEquals(accumulatedVotes); + this.accumulatedVotes.requireEquals(accumulatedVotes); let committedVotes = this.committedVotes.get(); - this.committedVotes.assertEquals(committedVotes); + this.committedVotes.requireEquals(committedVotes); let { state: newCommittedVotes, actionState: newAccumulatedVotes } = this.reducer.reduce( From 1459f66c70b38bfbbd9d02a54a963875a2c3bcf8 Mon Sep 17 00:00:00 2001 From: Saurabh Patil <55076843+Saurabhpatil-dev@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:08:54 +0530 Subject: [PATCH 0830/1215] :recycle: updated and tested changes for rest examples --- src/examples/zkapps/local_events_zkapp.ts | 2 +- src/examples/zkapps/set_local_preconditions_zkapp.ts | 2 +- src/examples/zkapps/simple_zkapp_with_proof.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examples/zkapps/local_events_zkapp.ts b/src/examples/zkapps/local_events_zkapp.ts index 5cec5d1a54..5608625689 100644 --- a/src/examples/zkapps/local_events_zkapp.ts +++ b/src/examples/zkapps/local_events_zkapp.ts @@ -40,7 +40,7 @@ class SimpleZkapp extends SmartContract { }); this.emitEvent('simpleEvent', y); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); this.x.set(x.add(y)); } } diff --git a/src/examples/zkapps/set_local_preconditions_zkapp.ts b/src/examples/zkapps/set_local_preconditions_zkapp.ts index 2b660e3333..2b11b2a494 100644 --- a/src/examples/zkapps/set_local_preconditions_zkapp.ts +++ b/src/examples/zkapps/set_local_preconditions_zkapp.ts @@ -26,7 +26,7 @@ await isReady; class SimpleZkapp extends SmartContract { @method blockheightEquals(y: UInt32) { let length = this.network.blockchainLength.get(); - this.network.blockchainLength.assertEquals(length); + this.network.blockchainLength.requireEquals(length); length.assertEquals(y); } diff --git a/src/examples/zkapps/simple_zkapp_with_proof.ts b/src/examples/zkapps/simple_zkapp_with_proof.ts index 78365ae19c..8fbfaeee59 100644 --- a/src/examples/zkapps/simple_zkapp_with_proof.ts +++ b/src/examples/zkapps/simple_zkapp_with_proof.ts @@ -39,7 +39,7 @@ class NotSoSimpleZkapp extends SmartContract { oldProof.verify(); trivialProof.verify(); let x = this.x.get(); - this.x.assertEquals(x); + this.x.requireEquals(x); this.x.set(x.add(y)); } } From 6632eba4e277e0d6f8b70576d8b2c90243c1c3a4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 08:19:06 +0100 Subject: [PATCH 0831/1215] test fixes --- src/bindings | 2 +- src/lib/testing/testing.unit-test.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/bindings b/src/bindings index 544c8a7306..d307af5e58 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 544c8a730618c9b912037abd7703755ef8b4c84d +Subproject commit d307af5e584c3b49aba6bf9c2735b0cfbfb5d91c diff --git a/src/lib/testing/testing.unit-test.ts b/src/lib/testing/testing.unit-test.ts index 4a0abe91fb..0c5859f829 100644 --- a/src/lib/testing/testing.unit-test.ts +++ b/src/lib/testing/testing.unit-test.ts @@ -20,10 +20,11 @@ test(Random.accountUpdate, (accountUpdate, assert) => { jsonString === JSON.stringify(AccountUpdate.toJSON(AccountUpdate.fromJSON(json))) ); - let fields = AccountUpdate.toFields(accountUpdate); - let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); - let recovered = AccountUpdate.fromFields(fields, auxiliary); - assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); + // TODO add back using `fromValue` + // let fields = AccountUpdate.toFields(accountUpdate); + // let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); + // let recovered = AccountUpdate.fromFields(fields, auxiliary); + // assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); }); test(Random.json.accountUpdate, (json) => { let jsonString = JSON.stringify(json); From 5dc5751bb1a729214342cff097cb6eaf02e7c60a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 09:37:15 +0100 Subject: [PATCH 0832/1215] make empty value required --- src/bindings | 2 +- src/lib/circuit_value.ts | 10 ++++++++++ src/lib/hash-generic.ts | 2 +- src/lib/hash.ts | 3 +++ src/lib/int.ts | 4 ++-- src/lib/provable.ts | 9 +++++++++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/bindings b/src/bindings index d307af5e58..161b82c520 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d307af5e584c3b49aba6bf9c2735b0cfbfb5d91c +Subproject commit 161b82c52098fa7186933f14ec9e63577a0ce4c9 diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 0f4106ebb9..2ac58ad54b 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -49,6 +49,7 @@ type ProvableExtension = { toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; toJSON: (x: T) => TJson; fromJSON: (x: TJson) => T; + emptyValue: () => T; }; type ProvableExtended = Provable & @@ -246,6 +247,15 @@ abstract class CircuitValue { } return Object.assign(Object.create(this.prototype), props); } + + static emptyValue(): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields ?? []; + let props: any = {}; + fields.forEach(([key, propType]) => { + props[key] = propType.emptyValue(); + }); + return Object.assign(Object.create(this.prototype), props); + } } function prop(this: any, target: any, key: string) { diff --git a/src/lib/hash-generic.ts b/src/lib/hash-generic.ts index c2907a3963..7e76c8ceee 100644 --- a/src/lib/hash-generic.ts +++ b/src/lib/hash-generic.ts @@ -1,4 +1,4 @@ -import { GenericField, GenericSignableField } from '../bindings/lib/generic.js'; +import { GenericSignableField } from '../bindings/lib/generic.js'; import { prefixToField } from '../bindings/lib/binable.js'; export { createHashHelpers, HashHelpers }; diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 684b7632ce..7a56b95b56 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -179,6 +179,9 @@ const TokenSymbolPure: ProvableExtended< toInput({ field }) { return { packed: [[field, 48]] }; }, + emptyValue() { + return { symbol: '', field: Field(0n) }; + }, }; class TokenSymbol extends Struct(TokenSymbolPure) { static get empty() { diff --git a/src/lib/int.ts b/src/lib/int.ts index a48118fa2c..10a7813a3b 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -723,8 +723,8 @@ class Sign extends CircuitValue { // x^2 === 1 <=> x === 1 or x === -1 x.value.square().assertEquals(Field(1)); } - static emptyValue(): Sign { - return Sign.one; + static emptyValue(): InstanceType { + return Sign.one as any; } static toInput(x: Sign): HashInput { return { packed: [[x.isPositive().toField(), 1]] }; diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 0b1a5e00aa..b3fa385eda 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -566,5 +566,14 @@ function provableArray>( HashInput.empty ); }, + + emptyValue() { + if (!('emptyValue' in type)) { + throw Error( + 'circuitArray.emptyValue: element type has no emptyValue method' + ); + } + return Array(length).fill(type.emptyValue()); + }, } satisfies ProvableExtended as any; } From 1c77343d6f9c6490207f7fdae5f38f048260ed3a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 09:48:21 +0100 Subject: [PATCH 0833/1215] minor simplification --- src/bindings | 2 +- src/lib/testing/random.ts | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/bindings b/src/bindings index 161b82c520..9d3506ea05 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 161b82c52098fa7186933f14ec9e63577a0ce4c9 +Subproject commit 9d3506ea05aecd1bb054859909f13736a9bd4e61 diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 65f0c1d4dc..4e1f4e3669 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -26,7 +26,7 @@ import { import { genericLayoutFold } from '../../bindings/lib/from-layout-signable.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { - GenericSignable, + PrimitiveTypeMap, primitiveTypeMap, } from '../../bindings/lib/generic.js'; import { Scalar, PrivateKey, Group } from '../../provable/curve-bigint.js'; @@ -35,10 +35,7 @@ import { randomBytes } from '../../bindings/crypto/random.js'; import { alphabet } from '../base58.js'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { Memo } from '../../mina-signer/src/memo.js'; -import { - ProvableExtended, - Signable, -} from '../../bindings/lib/provable-bigint.js'; +import { Signable } from '../../bindings/lib/provable-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; @@ -114,11 +111,11 @@ const verificationKeyHash = oneOf(VerificationKeyHash.emptyValue(), field); const receiptChainHash = oneOf(ReceiptChainHash.emptyValue(), field); const zkappUri = map(string(nat(50)), ZkappUri.fromJSON); -const PrimitiveMap = primitiveTypeMap(); -type Types = typeof TypeMap & typeof customTypes & typeof PrimitiveMap; -type Signable_ = GenericSignable; +type Types = typeof TypeMap & typeof customTypes & PrimitiveTypeMap; type Generators = { - [K in keyof Types]: Types[K] extends Signable_ ? Random : never; + [K in keyof Types]: Types[K] extends Signable + ? Random + : never; }; const Generators: Generators = { Field: field, @@ -141,8 +138,8 @@ const Generators: Generators = { string: base58(nat(50)), // TODO replace various strings, like signature, with parsed types number: nat(3), }; -let typeToBigintGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToBigintGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, Generators[key as keyof Generators]]) @@ -244,8 +241,8 @@ const JsonGenerators: JsonGenerators = { string: base58(nat(50)), number: nat(3), }; -let typeToJsonGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToJsonGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, JsonGenerators[key as keyof JsonGenerators]]) From 5b8cd24956db2c6dbf9b32ffa41114b4da4a2ecd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 10:23:31 +0100 Subject: [PATCH 0834/1215] adapt to bindings --- src/bindings | 2 +- src/lib/mina/account.ts | 10 +++------- src/lib/testing/random.ts | 10 ++++++++-- src/lib/testing/testing.unit-test.ts | 6 +++--- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/bindings b/src/bindings index 9d3506ea05..e767497368 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9d3506ea05aecd1bb054859909f13736a9bd4e61 +Subproject commit e7674973687e8b4ffadfa5652e839aa34c8a7d17 diff --git a/src/lib/mina/account.ts b/src/lib/mina/account.ts index c45d38587b..7025b37508 100644 --- a/src/lib/mina/account.ts +++ b/src/lib/mina/account.ts @@ -10,6 +10,7 @@ import { TypeMap, } from '../../bindings/mina-transaction/gen/transaction.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; +import { ProvableExtended } from '../circuit_value.js'; export { FetchedAccount, Account, PartialAccount }; export { accountQuery, parseFetchedAccount, fillPartialAccount }; @@ -184,19 +185,14 @@ function parseFetchedAccount({ } function fillPartialAccount(account: PartialAccount): Account { - return genericLayoutFold( + return genericLayoutFold>( TypeMap, customTypes, { map(type, value) { // if value exists, use it; otherwise fall back to dummy value if (value !== undefined) return value; - // fall back to dummy value - if (type.emptyValue) return type.emptyValue(); - return type.fromFields( - Array(type.sizeInFields()).fill(Field(0)), - type.toAuxiliary() - ); + return type.emptyValue(); }, reduceArray(array) { return array; diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 4e1f4e3669..4821df520b 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -23,7 +23,7 @@ import { PublicKey, StateHash, } from '../../bindings/mina-transaction/transaction-leaves-bigint.js'; -import { genericLayoutFold } from '../../bindings/lib/from-layout-signable.js'; +import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { PrimitiveTypeMap, @@ -329,7 +329,13 @@ function generatorFromLayout( { isJson }: { isJson: boolean } ): Random { let typeToGenerator = isJson ? typeToJsonGenerator : typeToBigintGenerator; - return genericLayoutFold, TypeMap, Json.TypeMap>( + return genericLayoutFold< + Signable, + undefined, + Random, + TypeMap, + Json.TypeMap + >( TypeMap, customTypes, { diff --git a/src/lib/testing/testing.unit-test.ts b/src/lib/testing/testing.unit-test.ts index 0c5859f829..6041b155b4 100644 --- a/src/lib/testing/testing.unit-test.ts +++ b/src/lib/testing/testing.unit-test.ts @@ -6,11 +6,11 @@ import { PublicKey, UInt32, UInt64, - provableFromLayout, + signableFromLayout, ZkappCommand, Json, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { test, Random, sample } from './property.js'; +import { test, Random } from './property.js'; // some trivial roundtrip tests test(Random.accountUpdate, (accountUpdate, assert) => { @@ -53,7 +53,7 @@ test.custom({ negative: true, timeBudget: 1000 })( AccountUpdate.fromJSON ); -const FeePayer = provableFromLayout< +const FeePayer = signableFromLayout< ZkappCommand['feePayer'], Json.ZkappCommand['feePayer'] >(jsLayout.ZkappCommand.entries.feePayer as any); From 32fd4a3c3bb0787f55e9a911fac38a7279e343ad Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 11:43:52 +0100 Subject: [PATCH 0835/1215] adapt type generation script --- src/build/jsLayoutToTypes.mjs | 55 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/build/jsLayoutToTypes.mjs b/src/build/jsLayoutToTypes.mjs index 2f0b87caef..be9648bf51 100644 --- a/src/build/jsLayoutToTypes.mjs +++ b/src/build/jsLayoutToTypes.mjs @@ -106,11 +106,22 @@ function writeType(typeData, isJson, withTypeMap) { }; } -function writeTsContent(types, isJson, leavesRelPath) { +function writeTsContent({ + jsLayout: types, + isJson, + isProvable, + leavesRelPath, +}) { let output = ''; let dependencies = new Set(); let converters = {}; let exports = new Set(isJson ? [] : ['customTypes']); + + let fromLayout = isProvable ? 'provableFromLayout' : 'signableFromLayout'; + let FromLayout = isProvable ? 'ProvableFromLayout' : 'SignableFromLayout'; + let GenericType = isProvable ? 'GenericProvableExtended' : 'GenericSignable'; + let GeneratedType = isProvable ? 'ProvableExtended' : 'Signable'; + for (let [Type, value] of Object.entries(types)) { let inner = writeType(value, isJson); exports.add(Type); @@ -118,7 +129,7 @@ function writeTsContent(types, isJson, leavesRelPath) { mergeObject(converters, inner.converters); output += `type ${Type} = ${inner.output};\n\n`; if (!isJson) { - output += `let ${Type} = provableFromLayout<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; + output += `let ${Type} = ${fromLayout}<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; } } @@ -135,8 +146,8 @@ function writeTsContent(types, isJson, leavesRelPath) { import { ${[...imports].join(', ')} } from '${importPath}'; ${ !isJson - ? "import { GenericProvableExtended } from '../../lib/generic.js';\n" + - "import { ProvableFromLayout, GenericLayout } from '../../lib/from-layout.js';\n" + + ? `import { ${GenericType} } from '../../lib/generic.js';\n` + + `import { ${FromLayout}, GenericLayout } from '../../lib/from-layout.js';\n` + "import * as Json from './transaction-json.js';\n" + "import { jsLayout } from './js-layout.js';\n" : '' @@ -147,7 +158,7 @@ ${ !isJson ? 'export { Json };\n' + `export * from '${leavesRelPath}';\n` + - 'export { provableFromLayout, toJSONEssential, emptyValue, Layout, TypeMap };\n' + `export { ${fromLayout}, toJSONEssential, emptyValue, Layout, TypeMap };\n` : `export * from '${leavesRelPath}';\n` + 'export { TypeMap };\n' } @@ -158,7 +169,7 @@ ${ (!isJson || '') && ` const TypeMap: { - [K in keyof TypeMap]: ProvableExtended; + [K in keyof TypeMap]: ${GeneratedType}; } = { ${[...typeMapKeys].join(', ')} } @@ -168,14 +179,14 @@ const TypeMap: { ${ (!isJson || '') && ` -type ProvableExtended = GenericProvableExtended; +type ${GeneratedType} = ${GenericType}; type Layout = GenericLayout; type CustomTypes = { ${customTypes - .map((c) => `${c.typeName}: ProvableExtended<${c.type}, ${c.jsonType}>;`) + .map((c) => `${c.typeName}: ${GeneratedType}<${c.type}, ${c.jsonType}>;`) .join(' ')} } let customTypes: CustomTypes = { ${customTypeNames.join(', ')} }; -let { provableFromLayout, toJSONEssential, emptyValue } = ProvableFromLayout< +let { ${fromLayout}, toJSONEssential, emptyValue } = ${FromLayout}< TypeMap, Json.TypeMap >(TypeMap, customTypes); @@ -196,25 +207,27 @@ async function writeTsFile(content, relPath) { let genPath = '../../bindings/mina-transaction/gen'; await ensureDir(genPath); -let jsonTypesContent = writeTsContent( +let jsonTypesContent = writeTsContent({ jsLayout, - true, - '../transaction-leaves-json.js' -); + isJson: true, + leavesRelPath: '../transaction-leaves-json.js', +}); await writeTsFile(jsonTypesContent, `${genPath}/transaction-json.ts`); -let jsTypesContent = writeTsContent( +let jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves.js' -); + isJson: false, + isProvable: true, + leavesRelPath: '../transaction-leaves.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction.ts`); -jsTypesContent = writeTsContent( +jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves-bigint.js' -); + isJson: false, + isProvable: false, + leavesRelPath: '../transaction-leaves-bigint.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction-bigint.ts`); await writeTsFile( From 563e9a585bb82e069dc31eae08665328f1109b7d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 11:48:11 +0100 Subject: [PATCH 0836/1215] use signable for private key --- src/provable/curve-bigint.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/provable/curve-bigint.ts b/src/provable/curve-bigint.ts index bdb11d9640..f4d18dc4a9 100644 --- a/src/provable/curve-bigint.ts +++ b/src/provable/curve-bigint.ts @@ -11,7 +11,6 @@ import { Bool, checkRange, Field, pseudoClass } from './field-bigint.js'; import { BinableBigint, ProvableBigint, - provable, signable, } from '../bindings/lib/provable-bigint.js'; import { HashInputLegacy } from './poseidon-bigint.js'; @@ -138,7 +137,7 @@ let Base58PrivateKey = base58(BinablePrivateKey, versionBytes.privateKey); */ const PrivateKey = { ...Scalar, - ...provable(Scalar), + ...signable(Scalar), ...Base58PrivateKey, ...BinablePrivateKey, toPublicKey(key: PrivateKey) { From b1d1cd7fcb16168800894fccdac9425eb5cbb534 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 11:48:14 +0100 Subject: [PATCH 0837/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index cd8146bb18..a92bf1a2e9 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit cd8146bb1897eca5c90f11df2611ea2451d42cc4 +Subproject commit a92bf1a2e9cbe5c4fe6775698df15809978a2f5c From 2e76d384025bc11be88da5b0436f23940ad16a0a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 11:52:29 +0100 Subject: [PATCH 0838/1215] fix: don't use Array.fill bc it breaks mutation --- src/lib/provable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/provable.ts b/src/lib/provable.ts index b3fa385eda..cd825d04db 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -573,7 +573,7 @@ function provableArray>( 'circuitArray.emptyValue: element type has no emptyValue method' ); } - return Array(length).fill(type.emptyValue()); + return Array.from({ length }, () => type.emptyValue()); }, } satisfies ProvableExtended as any; } From 000b03f97eafc7f8544d6c5b761f936be967d156 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 12:14:22 +0100 Subject: [PATCH 0839/1215] rename emptyValue() to empty() --- src/bindings | 2 +- src/build/jsLayoutToTypes.mjs | 4 ++-- src/lib/account_update.ts | 4 ++-- src/lib/bool.ts | 2 +- src/lib/circuit_value.ts | 6 +++--- src/lib/events.ts | 16 +++++++--------- src/lib/field.ts | 2 +- src/lib/hash.ts | 2 +- src/lib/int.ts | 2 +- src/lib/mina.ts | 2 +- src/lib/mina/account.ts | 2 +- src/lib/provable.ts | 10 ++++------ src/lib/signature.ts | 4 ++-- src/lib/testing/random.ts | 12 ++++++------ src/mina-signer/MinaSigner.ts | 4 ++-- src/mina-signer/src/memo.ts | 2 +- src/mina-signer/src/sign-zkapp-command.ts | 2 +- .../src/sign-zkapp-command.unit-test.ts | 2 +- src/mina-signer/src/signature.unit-test.ts | 2 +- src/mina-signer/tests/zkapp.unit-test.ts | 2 +- src/provable/field-bigint.ts | 2 +- 21 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/bindings b/src/bindings index a92bf1a2e9..1dd31581aa 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a92bf1a2e9cbe5c4fe6775698df15809978a2f5c +Subproject commit 1dd31581aacb3e6d76422063f052ea88c1451e1d diff --git a/src/build/jsLayoutToTypes.mjs b/src/build/jsLayoutToTypes.mjs index be9648bf51..d76907da9b 100644 --- a/src/build/jsLayoutToTypes.mjs +++ b/src/build/jsLayoutToTypes.mjs @@ -158,7 +158,7 @@ ${ !isJson ? 'export { Json };\n' + `export * from '${leavesRelPath}';\n` + - `export { ${fromLayout}, toJSONEssential, emptyValue, Layout, TypeMap };\n` + `export { ${fromLayout}, toJSONEssential, empty, Layout, TypeMap };\n` : `export * from '${leavesRelPath}';\n` + 'export { TypeMap };\n' } @@ -186,7 +186,7 @@ type CustomTypes = { ${customTypes .map((c) => `${c.typeName}: ${GeneratedType}<${c.type}, ${c.jsonType}>;`) .join(' ')} } let customTypes: CustomTypes = { ${customTypeNames.join(', ')} }; -let { ${fromLayout}, toJSONEssential, emptyValue } = ${FromLayout}< +let { ${fromLayout}, toJSONEssential, empty } = ${FromLayout}< TypeMap, Json.TypeMap >(TypeMap, customTypes); diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 292b77d0cb..c1938bd5e8 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -445,7 +445,7 @@ const Body = { tokenId?: Field, mayUseToken?: MayUseToken ): Body { - let { body } = Types.AccountUpdate.emptyValue(); + let { body } = Types.AccountUpdate.empty(); body.publicKey = publicKey; if (tokenId) { body.tokenId = tokenId; @@ -463,7 +463,7 @@ const Body = { }, dummy(): Body { - return Types.AccountUpdate.emptyValue().body; + return Types.AccountUpdate.empty().body; }, }; diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 15d0db2548..4abaeead51 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -295,7 +295,7 @@ class Bool { return 1; } - static emptyValue() { + static empty() { return new Bool(false); } diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 2ac58ad54b..85ac7c28ba 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -49,7 +49,7 @@ type ProvableExtension = { toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; toJSON: (x: T) => TJson; fromJSON: (x: TJson) => T; - emptyValue: () => T; + empty: () => T; }; type ProvableExtended = Provable & @@ -248,11 +248,11 @@ abstract class CircuitValue { return Object.assign(Object.create(this.prototype), props); } - static emptyValue(): InstanceType { + static empty(): InstanceType { const fields: [string, any][] = (this as any).prototype._fields ?? []; let props: any = {}; fields.forEach(([key, propType]) => { - props[key] = propType.emptyValue(); + props[key] = propType.empty(); }); return Object.assign(Object.create(this.prototype), props); } diff --git a/src/lib/events.ts b/src/lib/events.ts index 04efe23185..70fce76400 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -60,7 +60,7 @@ function createEvents({ const EventsProvable = { ...Events, ...dataAsHash({ - emptyValue: Events.empty, + empty: Events.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -107,7 +107,7 @@ function createEvents({ const SequenceEventsProvable = { ...Actions, ...dataAsHash({ - emptyValue: Actions.empty, + empty: Actions.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -123,18 +123,16 @@ function createEvents({ } function dataAsHash({ - emptyValue, + empty, toJSON, fromJSON, }: { - emptyValue: () => { data: T; hash: Field }; + empty: () => { data: T; hash: Field }; toJSON: (value: T) => J; fromJSON: (json: J) => { data: T; hash: Field }; -}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> & { - emptyValue(): { data: T; hash: Field }; -} { +}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> { return { - emptyValue, + empty, sizeInFields() { return 1; }, @@ -142,7 +140,7 @@ function dataAsHash({ return [hash]; }, toAuxiliary(value) { - return [value?.data ?? emptyValue().data]; + return [value?.data ?? empty().data]; }, fromFields([hash], [data]) { return { data, hash }; diff --git a/src/lib/field.ts b/src/lib/field.ts index fe117f65ab..6df164143b 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1148,7 +1148,7 @@ class Field { // ProvableExtended - static emptyValue() { + static empty() { return new Field(0n); } diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 7a56b95b56..c6d66d3f9d 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -179,7 +179,7 @@ const TokenSymbolPure: ProvableExtended< toInput({ field }) { return { packed: [[field, 48]] }; }, - emptyValue() { + empty() { return { symbol: '', field: Field(0n) }; }, }; diff --git a/src/lib/int.ts b/src/lib/int.ts index 10a7813a3b..45ca4e4011 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -723,7 +723,7 @@ class Sign extends CircuitValue { // x^2 === 1 <=> x === 1 or x === -1 x.value.square().assertEquals(Field(1)); } - static emptyValue(): InstanceType { + static empty(): InstanceType { return Sign.one as any; } static toInput(x: Sign): HashInput { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 28d6e3e7bf..d4a55c6a8b 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -1240,7 +1240,7 @@ function getProofsEnabled() { } function dummyAccount(pubkey?: PublicKey): Account { - let dummy = Types.Account.emptyValue(); + let dummy = Types.Account.empty(); if (pubkey) dummy.publicKey = pubkey; return dummy; } diff --git a/src/lib/mina/account.ts b/src/lib/mina/account.ts index 7025b37508..dc31d0d864 100644 --- a/src/lib/mina/account.ts +++ b/src/lib/mina/account.ts @@ -192,7 +192,7 @@ function fillPartialAccount(account: PartialAccount): Account { map(type, value) { // if value exists, use it; otherwise fall back to dummy value if (value !== undefined) return value; - return type.emptyValue(); + return type.empty(); }, reduceArray(array) { return array; diff --git a/src/lib/provable.ts b/src/lib/provable.ts index cd825d04db..74992a9568 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -567,13 +567,11 @@ function provableArray>( ); }, - emptyValue() { - if (!('emptyValue' in type)) { - throw Error( - 'circuitArray.emptyValue: element type has no emptyValue method' - ); + empty() { + if (!('empty' in type)) { + throw Error('circuitArray.empty: element type has no empty() method'); } - return Array.from({ length }, () => type.emptyValue()); + return Array.from({ length }, () => type.empty()); }, } satisfies ProvableExtended as any; } diff --git a/src/lib/signature.ts b/src/lib/signature.ts index e26e68ca2b..58d68ccef0 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -165,8 +165,8 @@ class PublicKey extends CircuitValue { * Creates an empty {@link PublicKey}. * @returns an empty {@link PublicKey} */ - static empty() { - return PublicKey.from({ x: Field(0), isOdd: Bool(false) }); + static empty(): InstanceType { + return PublicKey.from({ x: Field(0), isOdd: Bool(false) }) as any; } /** diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 4821df520b..2880ec51f8 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -5,7 +5,7 @@ import { Json, AccountUpdate, ZkappCommand, - emptyValue, + empty, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import { AuthRequired, @@ -81,7 +81,7 @@ const keypair = map(privateKey, (privatekey) => ({ publicKey: PrivateKey.toPublicKey(privatekey), })); -const tokenId = oneOf(TokenId.emptyValue(), field); +const tokenId = oneOf(TokenId.empty(), field); const stateHash = field; const authRequired = map( oneOf( @@ -106,9 +106,9 @@ const actions = mapWithInvalid( array(array(field, int(1, 5)), nat(2)), Actions.fromList ); -const actionState = oneOf(ActionState.emptyValue(), field); -const verificationKeyHash = oneOf(VerificationKeyHash.emptyValue(), field); -const receiptChainHash = oneOf(ReceiptChainHash.emptyValue(), field); +const actionState = oneOf(ActionState.empty(), field); +const verificationKeyHash = oneOf(VerificationKeyHash.empty(), field); +const receiptChainHash = oneOf(ReceiptChainHash.empty(), field); const zkappUri = map(string(nat(50)), ZkappUri.fromJSON); type Types = typeof TypeMap & typeof customTypes & PrimitiveTypeMap; @@ -365,7 +365,7 @@ function generatorFromLayout( } else { return mapWithInvalid(isSome, value, (isSome, value) => { let isSomeBoolean = TypeMap.Bool.toJSON(isSome); - if (!isSomeBoolean) return emptyValue(typeData); + if (!isSomeBoolean) return empty(typeData); return { isSome, value }; }); } diff --git a/src/mina-signer/MinaSigner.ts b/src/mina-signer/MinaSigner.ts index 36c94e3acb..d4dce0e2df 100644 --- a/src/mina-signer/MinaSigner.ts +++ b/src/mina-signer/MinaSigner.ts @@ -85,9 +85,9 @@ class Client { ) { throw Error('Public key not derivable from private key'); } - let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); + let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); dummy.feePayer.body.publicKey = publicKey; - dummy.memo = Memo.toBase58(Memo.emptyValue()); + dummy.memo = Memo.toBase58(Memo.empty()); let signed = signZkappCommand(dummy, privateKey, this.network); let ok = verifyZkappCommandSignature(signed, publicKey, this.network); if (!ok) throw Error('Could not sign a transaction with private key'); diff --git a/src/mina-signer/src/memo.ts b/src/mina-signer/src/memo.ts index 8b3ae05f6f..04538c3965 100644 --- a/src/mina-signer/src/memo.ts +++ b/src/mina-signer/src/memo.ts @@ -63,7 +63,7 @@ const Memo = { ...withBits(Binable, SIZE * 8), ...base58(Binable, versionBytes.userCommandMemo), sizeInBytes: SIZE, - emptyValue() { + empty() { return Memo.fromString(''); }, toValidString(memo = '') { diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 69578ed193..07d8a37c62 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -180,7 +180,7 @@ function accountUpdateFromFeePayer({ body: { fee, nonce, publicKey, validUntil }, authorization: signature, }: FeePayer): AccountUpdate { - let { body } = AccountUpdate.emptyValue(); + let { body } = AccountUpdate.empty(); body.publicKey = publicKey; body.balanceChange = { magnitude: fee, sgn: Sign(-1) }; body.incrementNonce = Bool(true); diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 7538dbd9ff..e400f9c8d4 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -62,7 +62,7 @@ test(Random.json.publicKey, (publicKeyBase58) => { }); // empty account update -let dummy = AccountUpdate.emptyValue(); +let dummy = AccountUpdate.empty(); let dummySnarky = AccountUpdateSnarky.dummy(); expect(AccountUpdate.toJSON(dummy)).toEqual( AccountUpdateSnarky.toJSON(dummySnarky) diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 2fb520f02c..9743bda23b 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -122,7 +122,7 @@ for (let i = 0; i < 10; i++) { [0xffff_ffff_ffff_ffffn, 64], ], }, - AccountUpdate.toInput(AccountUpdate.emptyValue()), + AccountUpdate.toInput(AccountUpdate.empty()), ]; for (let msg of messages) { checkCanVerify(msg, key, publicKey); diff --git a/src/mina-signer/tests/zkapp.unit-test.ts b/src/mina-signer/tests/zkapp.unit-test.ts index 64d83c2bca..5c2aaf6a3f 100644 --- a/src/mina-signer/tests/zkapp.unit-test.ts +++ b/src/mina-signer/tests/zkapp.unit-test.ts @@ -11,7 +11,7 @@ import { mocks } from '../../bindings/crypto/constants.js'; const client = new Client({ network: 'testnet' }); let { publicKey, privateKey } = client.genKeys(); -let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); +let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); let dummySignature = Signature.toBase58(Signature.dummy()); // we construct a transaction which needs signing of the fee payer and another account update diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index 88e6afd5cd..c23e0e0bc4 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -109,7 +109,7 @@ const Sign = pseudoClass( { ...ProvableBigint(checkSign), ...BinableBigint(1, checkSign), - emptyValue() { + empty() { return 1n; }, toInput(x: Sign): HashInput { From 3cd9e0bb18fb4ae760cedff4e35c248aacb3b833 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 12:18:55 +0100 Subject: [PATCH 0840/1215] expose empty on struct --- src/lib/circuit_value.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 85ac7c28ba..d0a85e5cc9 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -441,6 +441,15 @@ function Struct< let struct = Object.create(this.prototype); return Object.assign(struct, value); } + /** + * Create an instance of this struct filled with default values + * @returns an empty instance of this struct + */ + static empty(): T { + let value = this.type.empty(); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } /** * This method is for internal use, you will probably not need it. * Method to make assertions which should be always made whenever a struct of this type is created in a proof. From 206440df41f29a28f1896ff0bf9bfd54fad514b7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 12:41:37 +0100 Subject: [PATCH 0841/1215] expose ecdsa types --- src/lib/gadgets/elliptic-curve.ts | 4 ++-- src/lib/gadgets/gadgets.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index c237bcbd66..885f3e9adf 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -2,8 +2,8 @@ import { inverse, mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; import { assert, exists } from './common.js'; -import { Field3, ForeignField, split, weakBound } from './foreign-field.js'; -import { l, multiRangeCheck } from './range-check.js'; +import { Field3, ForeignField, split } from './foreign-field.js'; +import { l } from './range-check.js'; import { sha256 } from 'js-sha256'; import { bigIntToBits, diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 75466c9dc1..45954a263b 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -616,5 +616,15 @@ export namespace Gadgets { */ export type Sum = Sum_; } + + export namespace Ecdsa { + /** + * ECDSA signature consisting of two curve scalars. + */ + export type Signature = EcdsaSignature; + export type signature = ecdsaSignature; + } } type Sum_ = Sum; +type EcdsaSignature = Ecdsa.Signature; +type ecdsaSignature = Ecdsa.signature; From 8aff97a1315b4a831c9fe0d1565c3d3ead861b3a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 12:41:46 +0100 Subject: [PATCH 0842/1215] start ecdsa example --- src/examples/zkprogram/ecdsa/ecdsa.ts | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/examples/zkprogram/ecdsa/ecdsa.ts diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts new file mode 100644 index 0000000000..8f30703893 --- /dev/null +++ b/src/examples/zkprogram/ecdsa/ecdsa.ts @@ -0,0 +1,32 @@ +import { Gadgets, ZkProgram, Struct } from 'o1js'; + +export { ecdsaProgram }; + +let { ForeignField, Field3, Ecdsa } = Gadgets; + +type PublicKey = { x: Gadgets.Field3; y: Gadgets.Field3 }; +const PublicKey = Struct({ x: Field3.provable, y: Field3.provable }); + +const ecdsaProgram = ZkProgram({ + name: 'ecdsa', + publicInput: PublicKey, + + methods: { + verifyEcdsa: { + privateInputs: [Ecdsa.Signature.provable, Field3.provable], + method( + publicKey: PublicKey, + signature: Gadgets.Ecdsa.Signature, + msgHash: Gadgets.Field3 + ) { + // assert that private inputs are valid + ForeignField.assertAlmostFieldElements( + [signature.r, signature.s, msgHash], + 0n + ); + + Ecdsa.verify(Secp256k1, signature, msgHash, publicKey); + }, + }, + }, +}); From 05925b2aee58e7716aad8f0a584ca84eb85250a6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:03:30 +0100 Subject: [PATCH 0843/1215] expose elliptic curve intf on new Crypto namespace --- src/index.ts | 2 ++ src/lib/crypto.ts | 31 +++++++++++++++++++++++++++++++ src/lib/gadgets/gadgets.ts | 3 ++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/lib/crypto.ts diff --git a/src/index.ts b/src/index.ts index 5c617fced6..791e40e01c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,6 +81,8 @@ export { Nullifier } from './lib/nullifier.js'; import { ExperimentalZkProgram, ZkProgram } from './lib/proof_system.js'; export { ZkProgram }; +export { Crypto } from './lib/crypto.js'; + // experimental APIs import { Callback } from './lib/zkapp.js'; import { createChildAccountUpdate } from './lib/account_update.js'; diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts new file mode 100644 index 0000000000..3786284a9e --- /dev/null +++ b/src/lib/crypto.ts @@ -0,0 +1,31 @@ +import { CurveParams as CurveParams_ } from '../bindings/crypto/elliptic-curve-examples.js'; +import { + CurveAffine, + createCurveAffine, +} from '../bindings/crypto/elliptic_curve.js'; + +// crypto namespace +const Crypto = { + /** + * Create elliptic curve arithmetic methods. + */ + createCurve(params: Crypto.CurveParams): Crypto.Curve { + return createCurveAffine(params); + }, + /** + * Parameters defining an elliptic curve in short Weierstraß form + * y^2 = x^3 + ax + b + */ + CurveParams: CurveParams_, +}; + +namespace Crypto { + /** + * Parameters defining an elliptic curve in short Weierstraß form + * y^2 = x^3 + ax + b + */ + export type CurveParams = CurveParams_; + + export type Curve = CurveAffine; +} +export { Crypto }; diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 45954a263b..e7c97d77d3 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -11,6 +11,7 @@ import { Field } from '../core.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; import { Ecdsa, Point } from './elliptic-curve.js'; import { CurveAffine } from '../../bindings/crypto/elliptic_curve.js'; +import { Crypto } from '../crypto.js'; export { Gadgets }; @@ -583,7 +584,7 @@ const Gadgets = { * maybe assert that we are not running in provable context */ sign( - Curve: CurveAffine, + Curve: Crypto.Curve, msgHash: bigint, privateKey: bigint ): Ecdsa.signature { From 89f7d01050c2cbdcf2c23183caac4ec5201914cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:21:28 +0100 Subject: [PATCH 0844/1215] finish example --- src/examples/zkprogram/ecdsa/ecdsa.ts | 23 +++++++++++----- src/examples/zkprogram/ecdsa/run.ts | 38 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/examples/zkprogram/ecdsa/run.ts diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts index 8f30703893..183ec3e7f1 100644 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ b/src/examples/zkprogram/ecdsa/ecdsa.ts @@ -1,30 +1,39 @@ -import { Gadgets, ZkProgram, Struct } from 'o1js'; +import { Gadgets, ZkProgram, Struct, Crypto } from 'o1js'; -export { ecdsaProgram }; +export { ecdsaProgram, Point, Secp256k1 }; let { ForeignField, Field3, Ecdsa } = Gadgets; -type PublicKey = { x: Gadgets.Field3; y: Gadgets.Field3 }; -const PublicKey = Struct({ x: Field3.provable, y: Field3.provable }); +// TODO expose this as part of Gadgets.Curve + +class Point extends Struct({ x: Field3.provable, y: Field3.provable }) { + // point from bigints + static from({ x, y }: { x: bigint; y: bigint }) { + return new Point({ x: Field3.from(x), y: Field3.from(y) }); + } +} + +const Secp256k1 = Crypto.createCurve(Crypto.CurveParams.Secp256k1); const ecdsaProgram = ZkProgram({ name: 'ecdsa', - publicInput: PublicKey, + publicInput: Point, methods: { verifyEcdsa: { privateInputs: [Ecdsa.Signature.provable, Field3.provable], method( - publicKey: PublicKey, + publicKey: Point, signature: Gadgets.Ecdsa.Signature, msgHash: Gadgets.Field3 ) { // assert that private inputs are valid ForeignField.assertAlmostFieldElements( [signature.r, signature.s, msgHash], - 0n + Secp256k1.order ); + // verify signature Ecdsa.verify(Secp256k1, signature, msgHash, publicKey); }, }, diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts new file mode 100644 index 0000000000..936c896d50 --- /dev/null +++ b/src/examples/zkprogram/ecdsa/run.ts @@ -0,0 +1,38 @@ +import { Gadgets } from 'o1js'; +import { Point, Secp256k1, ecdsaProgram } from './ecdsa.js'; +import assert from 'assert'; + +// create an example ecdsa signature +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); + +// TODO make this use an actual keccak hash +let messageHash = Secp256k1.Scalar.random(); + +let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); + +console.time('ecdsa verify (build constraint system)'); +let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; +console.timeEnd('ecdsa verify (build constraint system)'); + +let gateTypes: Record = {}; +gateTypes['Total rows'] = cs.rows; +for (let gate of cs.gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]++; +} +console.log(gateTypes); + +console.time('ecdsa verify (compile)'); +await ecdsaProgram.compile(); +console.timeEnd('ecdsa verify (compile)'); + +console.time('ecdsa verify (prove)'); +let proof = await ecdsaProgram.verifyEcdsa( + Point.from(publicKey), + Gadgets.Ecdsa.Signature.from(signature), + Gadgets.Field3.from(messageHash) +); +console.timeEnd('ecdsa verify (prove)'); + +assert(await ecdsaProgram.verify(proof), 'proof verifies'); From 2094f6878e168c7ea84909e77510966d89ba6be2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:40:26 +0100 Subject: [PATCH 0845/1215] make vk returned by zkprogram consistent with smart contract --- src/index.ts | 2 +- src/lib/proof_system.ts | 21 +++++++++++++++------ src/lib/zkapp.ts | 18 +----------------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/index.ts b/src/index.ts index 791e40e01c..e47eb9e467 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,6 @@ export { method, declareMethods, Account, - VerificationKey, Reducer, } from './lib/zkapp.js'; export { state, State, declareState } from './lib/state.js'; @@ -45,6 +44,7 @@ export { Empty, Undefined, Void, + VerificationKey, } from './lib/proof_system.js'; export { Cache, CacheHeader } from './lib/proof-system/cache.js'; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 08adef5f68..1d3ace221a 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -18,6 +18,8 @@ import { FlexibleProvablePure, InferProvable, ProvablePureExtended, + Struct, + provable, provablePure, toConstant, } from './circuit_value.js'; @@ -25,7 +27,7 @@ import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; -import { MlArray, MlBool, MlResult, MlPair, MlUnit } from './ml/base.js'; +import { MlArray, MlBool, MlResult, MlPair } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { FieldConst, FieldVar } from './field.js'; import { Cache, readCache, writeCache } from './proof-system/cache.js'; @@ -47,6 +49,7 @@ export { Empty, Undefined, Void, + VerificationKey, }; // internal API @@ -260,10 +263,9 @@ function ZkProgram< } ): { name: string; - compile: (options?: { - cache?: Cache; - forceRecompile?: boolean; - }) => Promise<{ verificationKey: string }>; + compile: (options?: { cache?: Cache; forceRecompile?: boolean }) => Promise<{ + verificationKey: { data: string; hash: Field }; + }>; verify: ( proof: Proof< InferProvableOrUndefined>, @@ -361,7 +363,7 @@ function ZkProgram< overrideWrapDomain: config.overrideWrapDomain, }); compileOutput = { provers, verify }; - return { verificationKey: verificationKey.data }; + return { verificationKey }; } function toProver( @@ -485,6 +487,13 @@ class SelfProof extends Proof< PublicOutput > {} +class VerificationKey extends Struct({ + ...provable({ data: String, hash: Field }), + toJSON({ data }: { data: string }) { + return data; + }, +}) {} + function sortMethodArguments( programName: string, methodName: string, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a0df136baf..45dd3a0424 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -66,7 +66,6 @@ export { declareMethods, Callback, Account, - VerificationKey, Reducer, }; @@ -681,11 +680,7 @@ class SmartContract { // run methods once to get information that we need already at compile time let methodsMeta = this.analyzeMethods(); let gates = methodIntfs.map((intf) => methodsMeta[intf.methodName].gates); - let { - verificationKey: verificationKey_, - provers, - verify, - } = await compileProgram({ + let { verificationKey, provers, verify } = await compileProgram({ publicInputType: ZkappPublicInput, publicOutputType: Empty, methodIntfs, @@ -695,10 +690,6 @@ class SmartContract { cache, forceRecompile, }); - let verificationKey = { - data: verificationKey_.data, - hash: Field(verificationKey_.hash), - } satisfies VerificationKey; this._provers = provers; this._verificationKey = verificationKey; // TODO: instead of returning provers, return an artifact from which provers can be recovered @@ -1488,13 +1479,6 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number }; } -class VerificationKey extends Struct({ - ...provable({ data: String, hash: Field }), - toJSON({ data }: { data: string }) { - return data; - }, -}) {} - function selfAccountUpdate(zkapp: SmartContract, methodName?: string) { let body = Body.keepAll(zkapp.address, zkapp.tokenId); let update = new (AccountUpdate as any)(body, {}, true) as AccountUpdate; From 9ad5794371387b225e1b3586312783797a4ad442 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:40:34 +0100 Subject: [PATCH 0846/1215] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9aa81a19b..ecbdb943a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +# Breaking changes + +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1240 + # Added +- **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240 + - For an example, see `./src/examples/zkprogram/ecdsa` - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 - `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 From e7efc5d0e4dda1b5b2adeef3622b2f746c909022 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:42:28 +0100 Subject: [PATCH 0847/1215] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecbdb943a9..acab4eb011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240 - For an example, see `./src/examples/zkprogram/ecdsa` +- `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 - `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 From e0c43f779a59517f50ad4cdf4818c6c6f5d1b3e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 13:57:52 +0100 Subject: [PATCH 0848/1215] fix: use a variable for public key when analyzing ecdsa constraints --- src/lib/gadgets/ecdsa.unit-test.ts | 9 ++++++--- src/lib/gadgets/elliptic-curve.ts | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 47a4f0228e..144578c2cb 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -95,7 +95,7 @@ let msgHash = ); const ia = EllipticCurve.initialAggregator(Secp256k1); -const config = { G: { windowSize: 4 }, P: { windowSize: 4 }, ia }; +const config = { G: { windowSize: 4 }, P: { windowSize: 3 }, ia }; let program = ZkProgram({ name: 'ecdsa', @@ -119,11 +119,14 @@ let program = ZkProgram({ ecdsa: { privateInputs: [], method() { - let signature0 = Provable.witness( + let signature_ = Provable.witness( Ecdsa.Signature.provable, () => signature ); - Ecdsa.verify(Secp256k1, signature0, msgHash, publicKey, config); + let msgHash_ = Provable.witness(Field3.provable, () => msgHash); + let publicKey_ = Provable.witness(Point.provable, () => publicKey); + + Ecdsa.verify(Secp256k1, signature_, msgHash_, publicKey_, config); }, }, }, diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 885f3e9adf..da8bbe924d 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -149,7 +149,7 @@ function verifyEcdsa( G?: { windowSize: number; multiples?: Point[] }; P?: { windowSize: number; multiples?: Point[] }; ia?: point; - } = { G: { windowSize: 4 }, P: { windowSize: 4 } } + } = { G: { windowSize: 4 }, P: { windowSize: 3 } } ) { // constant case if ( From a2fe70f1ea3b5178a76d1b02b6582f08f703a0cb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:00:05 +0100 Subject: [PATCH 0849/1215] add vk regression test for ecdsa --- src/examples/zkprogram/ecdsa/run.ts | 2 +- tests/vk-regression/vk-regression.json | 13 +++++++++++++ tests/vk-regression/vk-regression.ts | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts index 936c896d50..9e3b5518fd 100644 --- a/src/examples/zkprogram/ecdsa/run.ts +++ b/src/examples/zkprogram/ecdsa/run.ts @@ -6,7 +6,7 @@ import assert from 'assert'; let privateKey = Secp256k1.Scalar.random(); let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); -// TODO make this use an actual keccak hash +// TODO use an actual keccak hash let messageHash = Secp256k1.Scalar.random(); let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index f60540c9f8..9c4f4f259a 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -201,5 +201,18 @@ "data": "", "hash": "" } + }, + "ecdsa": { + "digest": "1025b5f3a56c5366fd44d13f2678bba563c9581c6bacfde2b82a8dd49e33f2a2", + "methods": { + "verifyEcdsa": { + "rows": 38823, + "digest": "65c6f9efe1069f73adf3a842398f95d2" + } + }, + "verificationKey": { + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAPuFca4nCkavRK/kopNaYXLXQp30LgUt/V0UJqSuP4QXztIlWzZFWZ0ETZmroQESjM3Cl1C6O17KMs0QEJFJmQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgW9fGIdxPWc2TMCE7mIgqn1pcbKC1rjoMpStE7yiXTC4URGk/USFy0xf0tpXILTP7+nqebXCb+AvUnHe6cManJ146ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "2818165315298159349200200610054154136732885996642834698461943280515655745528" + } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 28a95542bd..a051f21137 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,6 +3,7 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; +import { ecdsaProgram } from '../../src/examples/zkprogram/ecdsa/ecdsa.js'; import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions @@ -38,6 +39,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ createDex().Dex, GroupCS, BitwiseCS, + ecdsaProgram, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; From 665158ba129f246241122dfb747fcfc166753767 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:12:34 +0100 Subject: [PATCH 0850/1215] expose cs printing --- src/examples/constraint_system.ts | 4 +- src/lib/provable-context.ts | 69 +++++++++++++++++++++++++++- src/lib/testing/constraint-system.ts | 56 +--------------------- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/examples/constraint_system.ts b/src/examples/constraint_system.ts index 6acb7fde53..f2da63ad3b 100644 --- a/src/examples/constraint_system.ts +++ b/src/examples/constraint_system.ts @@ -2,7 +2,7 @@ import { Field, Poseidon, Provable } from 'o1js'; let hash = Poseidon.hash([Field(1), Field(-1)]); -let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { +let { rows, digest, publicInputSize, print } = Provable.constraintSystem(() => { let x = Provable.witness(Field, () => Field(1)); let y = Provable.witness(Field, () => Field(-1)); x.add(y).assertEquals(Field(0)); @@ -10,5 +10,5 @@ let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { z.assertEquals(hash); }); -console.log(JSON.stringify(gates)); +print(); console.log({ rows, digest, publicInputSize }); diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 076e9f8300..0ce0030556 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -2,6 +2,7 @@ import { Context } from './global-context.js'; import { Gate, JsonGate, Snarky } from '../snarky.js'; import { parseHexString } from '../bindings/crypto/bigint-helpers.js'; import { prettifyStacktrace } from './errors.js'; +import { Fp } from '../bindings/crypto/finite_field.js'; // internal API export { @@ -17,6 +18,7 @@ export { inCompile, inCompileMode, gatesFromJson, + printGates, }; // global circuit-related context @@ -94,7 +96,16 @@ function constraintSystem(f: () => T) { result = f(); }); let { gates, publicInputSize } = gatesFromJson(json); - return { rows, digest, result: result! as T, gates, publicInputSize }; + return { + rows, + digest, + result: result! as T, + gates, + publicInputSize, + print() { + printGates(gates); + }, + }; } catch (error) { throw prettifyStacktrace(error); } finally { @@ -106,8 +117,62 @@ function constraintSystem(f: () => T) { function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: hexCoeffs }) => { - let coeffs = hexCoeffs.map(hex => parseHexString(hex).toString()); + let coeffs = hexCoeffs.map((hex) => parseHexString(hex).toString()); return { type: typ, wires, coeffs }; }); return { publicInputSize: cs.public_input_size, gates }; } + +// print a constraint system + +function printGates(gates: Gate[]) { + for (let i = 0, n = gates.length; i < n; i++) { + let { type, wires, coeffs } = gates[i]; + console.log( + i.toString().padEnd(4, ' '), + type.padEnd(15, ' '), + coeffsToPretty(type, coeffs).padEnd(30, ' '), + wiresToPretty(wires, i) + ); + } + console.log(); +} + +let minusRange = Fp.modulus - (1n << 64n); + +function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { + if (coeffs.length === 0) return ''; + if (type === 'Generic' && coeffs.length > 5) { + let first = coeffsToPretty(type, coeffs.slice(0, 5)); + let second = coeffsToPretty(type, coeffs.slice(5)); + return `${first} ${second}`; + } + if (type === 'Poseidon' && coeffs.length > 3) { + return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; + } + let str = coeffs + .map((c) => { + let c0 = BigInt(c); + if (c0 > minusRange) c0 -= Fp.modulus; + let cStr = c0.toString(); + if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; + return cStr; + }) + .join(' '); + return `[${str}]`; +} + +function wiresToPretty(wires: Gate['wires'], row: number) { + let strWires: string[] = []; + let n = wires.length; + for (let col = 0; col < n; col++) { + let wire = wires[col]; + if (wire.row === row && wire.col === col) continue; + if (wire.row === row) { + strWires.push(`${col}->${wire.col}`); + } else { + strWires.push(`${col}->(${wire.row},${wire.col})`); + } + } + return strWires.join(', '); +} diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index d39c6f6991..2922afa11d 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -12,6 +12,7 @@ import { Tuple } from '../util/types.js'; import { Random } from './random.js'; import { test } from './property.js'; import { Undefined, ZkProgram } from '../proof_system.js'; +import { printGates } from '../provable-context.js'; export { constraintSystem, @@ -27,7 +28,6 @@ export { withoutGenerics, print, repeat, - printGates, ConstraintSystemTest, }; @@ -446,57 +446,3 @@ type CsParams>> = { [k in keyof In]: InferCsVar; }; type TypeAndValue = { type: Provable; value: T }; - -// print a constraint system - -function printGates(gates: Gate[]) { - for (let i = 0, n = gates.length; i < n; i++) { - let { type, wires, coeffs } = gates[i]; - console.log( - i.toString().padEnd(4, ' '), - type.padEnd(15, ' '), - coeffsToPretty(type, coeffs).padEnd(30, ' '), - wiresToPretty(wires, i) - ); - } - console.log(); -} - -let minusRange = Field.ORDER - (1n << 64n); - -function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { - if (coeffs.length === 0) return ''; - if (type === 'Generic' && coeffs.length > 5) { - let first = coeffsToPretty(type, coeffs.slice(0, 5)); - let second = coeffsToPretty(type, coeffs.slice(5)); - return `${first} ${second}`; - } - if (type === 'Poseidon' && coeffs.length > 3) { - return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; - } - let str = coeffs - .map((c) => { - let c0 = BigInt(c); - if (c0 > minusRange) c0 -= Field.ORDER; - let cStr = c0.toString(); - if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; - return cStr; - }) - .join(' '); - return `[${str}]`; -} - -function wiresToPretty(wires: Gate['wires'], row: number) { - let strWires: string[] = []; - let n = wires.length; - for (let col = 0; col < n; col++) { - let wire = wires[col]; - if (wire.row === row && wire.col === col) continue; - if (wire.row === row) { - strWires.push(`${col}->${wire.col}`); - } else { - strWires.push(`${col}->(${wire.row},${wire.col})`); - } - } - return strWires.join(', '); -} From 71b7dcffac9550190f4f9cb8604b808e869566de Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:13:14 +0100 Subject: [PATCH 0851/1215] minor --- src/examples/zkprogram/ecdsa/run.ts | 5 +++++ src/lib/gadgets/elliptic-curve.unit-test.ts | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts index 9e3b5518fd..b1d502c7e9 100644 --- a/src/examples/zkprogram/ecdsa/run.ts +++ b/src/examples/zkprogram/ecdsa/run.ts @@ -3,6 +3,7 @@ import { Point, Secp256k1, ecdsaProgram } from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature + let privateKey = Secp256k1.Scalar.random(); let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); @@ -11,6 +12,8 @@ let messageHash = Secp256k1.Scalar.random(); let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); +// investigate the constraint system generated by ECDSA verify + console.time('ecdsa verify (build constraint system)'); let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; console.timeEnd('ecdsa verify (build constraint system)'); @@ -23,6 +26,8 @@ for (let gate of cs.gates) { } console.log(gateTypes); +// compile and prove + console.time('ecdsa verify (compile)'); await ecdsaProgram.compile(); console.timeEnd('ecdsa verify (compile)'); diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index 722ca3137a..00b555239d 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -1,7 +1,6 @@ import { Provable } from '../provable.js'; import { Field3 } from './foreign-field.js'; import { EllipticCurve } from './elliptic-curve.js'; -import { printGates } from '../testing/constraint-system.js'; import { assert } from './common.js'; import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; @@ -32,10 +31,10 @@ let csDouble = Provable.constraintSystem(() => { double(g, Secp256k1.modulus); }); -printGates(csAdd.gates); +csAdd.print(); console.log({ digest: csAdd.digest, rows: csAdd.rows }); -printGates(csDouble.gates); +csDouble.print(); console.log({ digest: csDouble.digest, rows: csDouble.rows }); let point = initialAggregator(Pallas); From febd9c9b4d625f05194a470ac59f6b604b15973d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:13:31 +0100 Subject: [PATCH 0852/1215] delete ec unit test --- src/lib/gadgets/elliptic-curve.unit-test.ts | 42 --------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/lib/gadgets/elliptic-curve.unit-test.ts diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts deleted file mode 100644 index 00b555239d..0000000000 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Provable } from '../provable.js'; -import { Field3 } from './foreign-field.js'; -import { EllipticCurve } from './elliptic-curve.js'; -import { assert } from './common.js'; -import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; - -const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); -const Pallas = createCurveAffine(CurveParams.Pallas); - -let { add, double, initialAggregator } = EllipticCurve; - -let csAdd = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let x2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y2 = Provable.witness(Field3.provable, () => Field3.from(0n)); - - let g = { x: x1, y: y1 }; - let h = { x: x2, y: y2 }; - - add(g, h, Secp256k1.modulus); -}); - -let csDouble = Provable.constraintSystem(() => { - let x1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - let y1 = Provable.witness(Field3.provable, () => Field3.from(0n)); - - let g = { x: x1, y: y1 }; - - double(g, Secp256k1.modulus); -}); - -csAdd.print(); -console.log({ digest: csAdd.digest, rows: csAdd.rows }); - -csDouble.print(); -console.log({ digest: csDouble.digest, rows: csDouble.rows }); - -let point = initialAggregator(Pallas); -console.log({ point }); -assert(Pallas.isOnCurve(point)); From f27f2e1c96d639e88b908864d1e4bf445108980e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:17:25 +0100 Subject: [PATCH 0853/1215] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acab4eb011..3f361304c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.account.x.getAndAssertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 - `this.account.x.assertBetween()` is now `this.account.x.requireBetween()` https://github.com/o1-labs/o1js/pull/1265 - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 +- `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `print()` method for pretty-printing the constraint system https://github.com/o1-labs/o1js/pull/1240 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From 191971da1d52dd2c8ba2cc5b56e806c63cf6b302 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:24:48 +0100 Subject: [PATCH 0854/1215] finish doccomments --- src/lib/gadgets/gadgets.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e7c97d77d3..bcfc0a41b2 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -551,20 +551,24 @@ const Gadgets = { }, /** - * TODO + * ECDSA verification gadget and helper methods. */ Ecdsa: { /** - * TODO + * Verify an ECDSA signature. * * @example * ```ts - * let Curve = Curves.Secp256k1; // TODO provide this somehow - * // TODO easy way to check that foreign field elements are valid - * let signature = { r, s }; - * // TODO need a way to check that publicKey is on curve - * let publicKey = { x, y }; + * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); * + * // assert that message hash and signature are valid scalar field elements + * Gadgets.ForeignField.assertAlmostFieldElements( + * [signature.r, signature.s, msgHash], + * Curve.order + * ); + * // TODO add an easy way to prove that the public key lies on the curve + * + * // verify signature * Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); * ``` */ @@ -578,10 +582,9 @@ const Gadgets = { }, /** - * TODO + * Sign a message hash using ECDSA. * - * should this be here, given that it's not a provable method? - * maybe assert that we are not running in provable context + * _This method is not provable._ */ sign( Curve: Crypto.Curve, From 6a449b25b1a6c59e0f685c173529a3add890fcca Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:24:54 +0100 Subject: [PATCH 0855/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index b67e80608e..b432ffd750 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b67e80608e467b791662ddc01965863848bb38b5 +Subproject commit b432ffd750b4ee961e4b9924f69b5e07bc5368a8 From d0dc254c533e5750c29f2baf7b066a349e9426b4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 15:11:14 +0100 Subject: [PATCH 0856/1215] fix unit test --- src/lib/proof_system.unit-test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index b89f1f3dd3..54b95e6d45 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -16,13 +16,15 @@ const EmptyProgram = ZkProgram({ }); const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); -expect(emptyMethodsMetadata.run).toEqual({ - rows: 0, - digest: '4f5ddea76d29cfcfd8c595f14e31f21b', - result: undefined, - gates: [], - publicInputSize: 0, -}); +expect(emptyMethodsMetadata.run).toEqual( + expect.objectContaining({ + rows: 0, + digest: '4f5ddea76d29cfcfd8c595f14e31f21b', + result: undefined, + gates: [], + publicInputSize: 0, + }) +); class CounterPublicInput extends Struct({ current: UInt64, From c99a92320c2aedf7844baa47b95a57f691e68109 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 15:23:44 +0100 Subject: [PATCH 0857/1215] fix bool.sizeInBits --- src/lib/bool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 4abaeead51..9d7fee9e4d 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -318,7 +318,7 @@ class Bool { return BoolBinable.readBytes(bytes, offset); } - static sizeInBytes: 1; + static sizeInBytes = 1; static check(x: Bool): void { Snarky.field.assertBoolean(x.value); From 62c1635c77c913d544fafb0cb5ff6d5bb941da82 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 28 Nov 2023 11:19:31 +0100 Subject: [PATCH 0858/1215] simplify tests --- src/lib/gadgets/arithmetic.unit-test.ts | 30 ++++++++++--------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts index 996a9389b7..e1ca27bd5a 100644 --- a/src/lib/gadgets/arithmetic.unit-test.ts +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -4,6 +4,7 @@ import { equivalentProvable as equivalent, equivalentAsync, field, + record, } from '../testing/equivalent.js'; import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; @@ -12,13 +13,6 @@ import { Random } from '../testing/property.js'; import { provable } from '../circuit_value.js'; import { assert } from './common.js'; -const maybeField = { - ...field, - rng: Random.map(Random.oneOf(Random.field, Random.field.invalid), (x) => - mod(x, Field.ORDER) - ), -}; - let Arithmetic = ZkProgram({ name: 'arithmetic', publicOutput: provable({ @@ -38,31 +32,31 @@ let Arithmetic = ZkProgram({ await Arithmetic.compile(); const divMod32Helper = (x: bigint) => { - let q = x / (1n << 32n); - let r = x - q * (1n << 32n); - return [r, q]; + let quotient = x / (1n << 32n); + let remainder = x - quotient * (1n << 32n); + return { remainder, quotient }; }; +const divMod32Output = record({ remainder: field, quotient: field }); -equivalent({ from: [maybeField], to: array(field, 2) })( +equivalent({ + from: [field], + to: divMod32Output, +})( (x) => { assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); return divMod32Helper(x); }, (x) => { - let { remainder, quotient } = Gadgets.divMod32(x); - return [remainder, quotient]; + return Gadgets.divMod32(x); } ); -await equivalentAsync({ from: [maybeField], to: array(field, 2) }, { runs: 3 })( +await equivalentAsync({ from: [field], to: divMod32Output }, { runs: 3 })( (x) => { assert(x < 1n << 64n, `x needs to fit in 64bit, but got ${x}`); return divMod32Helper(x); }, async (x) => { - let { - publicOutput: { quotient, remainder }, - } = await Arithmetic.divMod32(x); - return [remainder, quotient]; + return (await Arithmetic.divMod32(x)).publicOutput; } ); From 93be2b0b81e32d4e6bd5e16488d8229f026b1794 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 28 Nov 2023 11:53:35 +0100 Subject: [PATCH 0859/1215] refactor rangeCheckHelper --- src/lib/field.ts | 3 + src/lib/gadgets/gadgets.ts | 17 ++++++ src/lib/gadgets/range-check.ts | 39 +++++++++++-- src/lib/hash.ts | 3 +- src/lib/int.ts | 104 +++++++++++++++++++++------------ src/lib/string.ts | 3 +- 6 files changed, 125 insertions(+), 44 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index de52ecf621..438e1ed1f9 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -997,6 +997,9 @@ class Field { } /** + * + * @deprecated use `Gadgets.rangeCheckHelper` instead. + * * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. * * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index e1bb3f71ae..b0b06dabba 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -6,6 +6,7 @@ import { multiRangeCheck, rangeCheck64, rangeCheck32, + rangeCheckHelper, } from './range-check.js'; import { not, @@ -76,6 +77,22 @@ const Gadgets = { rangeCheck32(x: Field) { return rangeCheck32(x); }, + + /** + * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. + * + * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. + * + * As {@link Field} elements are represented using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness), + * the resulting {@link Field} element will equal the original one if it fits in `length` bits. + * + * @param length - The number of bits to take from this {@link Field} element. + * + * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. + */ + rangeCheckHelper(length: number, x: Field) { + return rangeCheckHelper(length, x); + }, /** * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 276f7a51af..2854be5053 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,8 +1,17 @@ +import { Snarky } from '../../snarky.js'; +import { Fp } from '../../bindings/crypto/finite_field.js'; +import { Field as FieldProvable } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { bitSlice, exists, toVar, toVars } from './common.js'; - -export { rangeCheck64, rangeCheck32, multiRangeCheck, compactMultiRangeCheck }; +import { assert, bitSlice, exists, toVar, toVars } from './common.js'; + +export { + rangeCheck64, + rangeCheck32, + multiRangeCheck, + compactMultiRangeCheck, + rangeCheckHelper, +}; export { l, l2, l3, lMask, l2Mask }; /** @@ -16,10 +25,32 @@ function rangeCheck32(x: Field) { return; } - let actual = x.rangeCheckHelper(32); + let actual = rangeCheckHelper(32, x); actual.assertEquals(x); } +/** + * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. + */ +function rangeCheckHelper(length: number, x: Field) { + assert( + length <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + assert(length > 0, `bit length must be positive, got ${length}`); + assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + + let lengthDiv16 = length / 16; + if (x.isConstant()) { + let bits = FieldProvable.toBits(x.toBigInt()) + .slice(0, length) + .concat(Array(Fp.sizeInBits - length).fill(false)); + return new Field(FieldProvable.fromBits(bits)); + } + let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); + return new Field(y); +} + /** * Asserts that x is in the range [0, 2^64) */ diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 684b7632ce..05d108197e 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -6,6 +6,7 @@ import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { Poseidon, TokenSymbol }; @@ -166,7 +167,7 @@ const TokenSymbolPure: ProvableExtended< return 1; }, check({ field }: TokenSymbol) { - let actual = field.rangeCheckHelper(48); + let actual = Gadgets.rangeCheckHelper(48, field); actual.assertEquals(field); }, toJSON({ symbol }) { diff --git a/src/lib/int.ts b/src/lib/int.ts index f9025437ee..281a3ee034 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,7 +3,15 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { Gadgets } from './gadgets/gadgets.js'; +import { + Gadgets, + Gadgets, + Gadgets, + Gadgets, + Gadgets, + Gadgets, + Gadgets, +} from './gadgets/gadgets.js'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -67,7 +75,7 @@ class UInt64 extends CircuitValue { } static check(x: UInt64) { - let actual = x.value.rangeCheckHelper(64); + let actual = Gadgets.rangeCheckHelper(UInt64.NUM_BITS, x.value); actual.assertEquals(x.value); } @@ -143,11 +151,11 @@ class UInt64 extends CircuitValue { () => new Field(x.toBigInt() / y_.toBigInt()) ); - q.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(q); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, q).assertEquals(q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - r.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(r); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, r).assertEquals(r); let r_ = new UInt64(r); let q_ = new UInt64(q); @@ -183,7 +191,7 @@ class UInt64 extends CircuitValue { */ mul(y: UInt64 | number) { let z = this.value.mul(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); return new UInt64(z); } @@ -192,7 +200,7 @@ class UInt64 extends CircuitValue { */ add(y: UInt64 | number) { let z = this.value.add(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); return new UInt64(z); } @@ -201,7 +209,7 @@ class UInt64 extends CircuitValue { */ sub(y: UInt64 | number) { let z = this.value.sub(UInt64.from(y).value); - z.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(z); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); return new UInt64(z); } @@ -391,12 +399,17 @@ class UInt64 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(yMinusX); + + let xMinusYFits = Gadgets.rangeCheckHelper( + UInt64.NUM_BITS, + xMinusY + ).equals(xMinusY); + + let yMinusXFits = Gadgets.rangeCheckHelper( + UInt64.NUM_BITS, + yMinusX + ).equals(yMinusX); + xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -412,12 +425,17 @@ class UInt64 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt64.NUM_BITS) - .equals(yMinusX); + + let xMinusYFits = Gadgets.rangeCheckHelper( + UInt64.NUM_BITS, + xMinusY + ).equals(xMinusY); + + let yMinusXFits = Gadgets.rangeCheckHelper( + UInt64.NUM_BITS, + yMinusX + ).equals(yMinusX); + xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -447,7 +465,10 @@ class UInt64 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - yMinusX.rangeCheckHelper(UInt64.NUM_BITS).assertEquals(yMinusX, message); + Gadgets.rangeCheckHelper(UInt64.NUM_BITS, yMinusX).assertEquals( + yMinusX, + message + ); } /** @@ -661,11 +682,11 @@ class UInt32 extends CircuitValue { () => new Field(x.toBigInt() / y_.toBigInt()) ); - q.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(q); + Gadgets.rangeCheck32(q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - r.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(r); + Gadgets.rangeCheck32(r); let r_ = new UInt32(r); let q_ = new UInt32(q); @@ -698,7 +719,7 @@ class UInt32 extends CircuitValue { */ mul(y: UInt32 | number) { let z = this.value.mul(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); + Gadgets.rangeCheck32(z); return new UInt32(z); } /** @@ -706,7 +727,7 @@ class UInt32 extends CircuitValue { */ add(y: UInt32 | number) { let z = this.value.add(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); + Gadgets.rangeCheck32(z); return new UInt32(z); } /** @@ -714,7 +735,7 @@ class UInt32 extends CircuitValue { */ sub(y: UInt32 | number) { let z = this.value.sub(UInt32.from(y).value); - z.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(z); + Gadgets.rangeCheck32(z); return new UInt32(z); } @@ -904,12 +925,14 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(yMinusX); + let xMinusYFits = Gadgets.rangeCheckHelper( + UInt32.NUM_BITS, + xMinusY + ).equals(xMinusY); + let yMinusXFits = Gadgets.rangeCheckHelper( + UInt32.NUM_BITS, + yMinusX + ).equals(yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -925,12 +948,14 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = xMinusY - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(xMinusY); - let yMinusXFits = yMinusX - .rangeCheckHelper(UInt32.NUM_BITS) - .equals(yMinusX); + let xMinusYFits = Gadgets.rangeCheckHelper( + UInt32.NUM_BITS, + xMinusY + ).equals(xMinusY); + let yMinusXFits = Gadgets.rangeCheckHelper( + UInt32.NUM_BITS, + yMinusX + ).equals(yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -960,7 +985,10 @@ class UInt32 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - yMinusX.rangeCheckHelper(UInt32.NUM_BITS).assertEquals(yMinusX, message); + Gadgets.rangeCheckHelper(UInt32.NUM_BITS, yMinusX).assertEquals( + yMinusX, + message + ); } /** diff --git a/src/lib/string.ts b/src/lib/string.ts index 623aa4a72e..031bbf51f8 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -2,6 +2,7 @@ import { Bool, Field } from '../lib/core.js'; import { arrayProp, CircuitValue, prop } from './circuit_value.js'; import { Provable } from './provable.js'; import { Poseidon } from './hash.js'; +import { Gadgets } from './gadgets/gadgets.js'; export { Character, CircuitString }; @@ -31,7 +32,7 @@ class Character extends CircuitValue { // TODO: Add support for more character sets // right now it's 16 bits because 8 not supported :/ static check(c: Character) { - c.value.rangeCheckHelper(16).assertEquals(c.value); + Gadgets.rangeCheckHelper(16, c.value).assertEquals(c.value); } } From 12f388d29582cf47a054a651eae5594b356dfa76 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 28 Nov 2023 11:56:11 +0100 Subject: [PATCH 0860/1215] fix import --- src/lib/int.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 281a3ee034..4ba440ed4c 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,15 +3,7 @@ import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { - Gadgets, - Gadgets, - Gadgets, - Gadgets, - Gadgets, - Gadgets, - Gadgets, -} from './gadgets/gadgets.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { UInt32, UInt64, Int64, Sign }; From 2b091e4c8806eb0b7455f93b19d7d583b58a2164 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 28 Nov 2023 12:18:16 +0100 Subject: [PATCH 0861/1215] changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd3e03aac..daa3d03bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +### Breaking changes + +- Rename `Gadgets.rotate` to `Gadgets.rotate64` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 + +### Added + +- `Gadgets.rotate32` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.divMod32` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rangeCheck32` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rangeCheckHelper` for returning a new Field element of the first `length` bits of an element https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.addMod32` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 +- Expose new bitwise gadgets on `UInt32` and `UInt64` https://github.com/o1-labs/o1js/pull/1259 + - bitwise XOR via `{UInt32, UInt64}.xor` + - bitwise NOT via `{UInt32, UInt64}.not` + - bitwise ROTATE via `{UInt32, UInt64}.rotate` + - bitwise LEFTSHIFT via `{UInt32, UInt64}.leftShift` + - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift` + - bitwise AND via `{UInt32, UInt64}.and` + ### Changed - Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. @@ -30,6 +49,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.account.x.assertNothing()` is now `this.account.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1265 - `this.currentSlot.assertBetween(x,y)` is now `this.currentSlot.requireBetween(x,y)` https://github.com/o1-labs/o1js/pull/1265 - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 +- Deprecate `field.rangeCheckHelper` in favor of `Gadgets.rangeCheckHelper` https://github.com/o1-labs/o1js/pull/1259 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From 53133d77a386ea69d5523a8c4f7e6bc134ec5d33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 15:56:59 +0100 Subject: [PATCH 0862/1215] merge glv and non-glv scalar mul --- src/lib/gadgets/elliptic-curve.ts | 161 +++++++++--------------------- 1 file changed, 46 insertions(+), 115 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index dfff746f55..bd48edb137 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -178,7 +178,7 @@ function verifyEcdsa( let u2 = ForeignField.mul(r, sInv, Curve.order); let G = Point.from(Curve.one); - let R = multiScalarMulGlv( + let R = multiScalarMul( Curve, [u1, u2], [G, publicKey], @@ -233,7 +233,6 @@ function verifyEcdsaConstant( * * TODO: could use lookups for picking precomputed multiples, instead of O(2^c) provable switch * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case - * TODO: glv trick which cuts down ec doubles by half by splitting s*P = s0*P + s1*endo(P) with s0, s1 in [0, 2^128) */ function multiScalarMul( Curve: CurveAffine, @@ -248,6 +247,7 @@ function multiScalarMul( let n = points.length; assert(scalars.length === n, 'Points and scalars lengths must match'); assertPositiveInteger(n, 'Expected at least 1 point and scalar'); + let useGlv = Curve.hasEndomorphism; // constant case if (scalars.every(Field3.isConstant) && points.every(Point.isConstant)) { @@ -256,7 +256,11 @@ function multiScalarMul( let P = points.map(Point.toBigint); let sum = Curve.zero; for (let i = 0; i < n; i++) { - sum = Curve.add(sum, Curve.scale(P[i], s[i])); + if (useGlv) { + sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); + } else { + sum = Curve.add(sum, Curve.scale(P[i], s[i])); + } } return Point.from(sum); } @@ -267,124 +271,51 @@ function multiScalarMul( getPointTable(Curve, P, windowSizes[i], tableConfigs[i]?.multiples) ); - // slice scalars - let b = Curve.order.toString(2).length; - let scalarChunks = scalars.map((s, i) => - slice(s, { maxBits: b, chunkSize: windowSizes[i] }) - ); - - ia ??= initialAggregator(Curve); - let sum = Point.from(ia); - - for (let i = b - 1; i >= 0; i--) { - // add in multiple of each point - for (let j = 0; j < n; j++) { - let windowSize = windowSizes[j]; - if (i % windowSize === 0) { - // pick point to add based on the scalar chunk - let sj = scalarChunks[j][i / windowSize]; - let sjP = - windowSize === 1 - ? points[j] - : arrayGetGeneric(Point.provable, tables[j], sj); - - // ec addition - let added = add(sum, sjP, Curve.modulus); - - // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) - sum = Provable.if(sj.equals(0), Point.provable, sum, added); - } - } - - if (i === 0) break; - - // jointly double all points - // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) - sum = double(sum, Curve.modulus); - } - - // the sum is now 2^(b-1)*IA + sum_i s_i*P_i - // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result - let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); - Provable.equal(Point.provable, sum, Point.from(iaFinal)).assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + let maxBits = Curve.Scalar.sizeInBits; - return sum; -} + if (useGlv) { + maxBits = Curve.Endo.decomposeMaxBits; + assert(maxBits < l2, 'decomposed scalars have to be < 2*88 bits'); -function multiScalarMulGlv( - Curve: CurveAffine, - scalars: Field3[], - points: Point[], - tableConfigs: ( - | { windowSize?: number; multiples?: Point[] } - | undefined - )[] = [], - ia?: point -): Point { - let n = points.length; - assert(scalars.length === n, 'Points and scalars lengths must match'); - assertPositiveInteger(n, 'Expected at least 1 point and scalar'); + // decompose scalars and handle signs + let n2 = 2 * n; + let scalars2: Field3[] = Array(n2); + let points2: Point[] = Array(n2); + let windowSizes2: number[] = Array(n2); + let tables2: Point[][] = Array(n2); + let mrcStack: Field[] = []; - // constant case - if (scalars.every(Field3.isConstant) && points.every(Point.isConstant)) { - // TODO dedicated MSM - let s = scalars.map(Field3.toBigint); - let P = points.map(Point.toBigint); - let sum = Curve.zero; for (let i = 0; i < n; i++) { - sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); - } - return Point.from(sum); - } - - // parse or build point tables - let windowSizes = points.map((_, i) => tableConfigs[i]?.windowSize ?? 1); - let tables = points.map((P, i) => - getPointTable(Curve, P, windowSizes[i], undefined) - ); - - let maxBits = Curve.Endo.decomposeMaxBits; - assert(maxBits < l2, 'decomposed scalars assumed to be < 2*88 bits'); - - // decompose scalars and handle signs - let n2 = 2 * n; - let scalars2: Field3[] = Array(n2); - let points2: Point[] = Array(n2); - let windowSizes2: number[] = Array(n2); - let tables2: Point[][] = Array(n2); - let mrcStack: Field[] = []; - - for (let i = 0; i < n; i++) { - let [s0, s1] = decomposeNoRangeCheck(Curve, scalars[i]); - scalars2[2 * i] = s0.abs; - scalars2[2 * i + 1] = s1.abs; - - let table = tables[i]; - let endoTable = table.map((P, i) => { - if (i === 0) return P; - let [phiP, betaXBound] = endomorphism(Curve, P); - mrcStack.push(betaXBound); - return phiP; - }); - tables2[2 * i] = table.map((P) => - negateIf(s0.isNegative, P, Curve.modulus) - ); - tables2[2 * i + 1] = endoTable.map((P) => - negateIf(s1.isNegative, P, Curve.modulus) - ); - points2[2 * i] = tables2[2 * i][1]; - points2[2 * i + 1] = tables2[2 * i + 1][1]; + let [s0, s1] = decomposeNoRangeCheck(Curve, scalars[i]); + scalars2[2 * i] = s0.abs; + scalars2[2 * i + 1] = s1.abs; + + let table = tables[i]; + let endoTable = table.map((P, i) => { + if (i === 0) return P; + let [phiP, betaXBound] = endomorphism(Curve, P); + mrcStack.push(betaXBound); + return phiP; + }); + tables2[2 * i] = table.map((P) => + negateIf(s0.isNegative, P, Curve.modulus) + ); + tables2[2 * i + 1] = endoTable.map((P) => + negateIf(s1.isNegative, P, Curve.modulus) + ); + points2[2 * i] = tables2[2 * i][1]; + points2[2 * i + 1] = tables2[2 * i + 1][1]; - windowSizes2[2 * i] = windowSizes2[2 * i + 1] = windowSizes[i]; + windowSizes2[2 * i] = windowSizes2[2 * i + 1] = windowSizes[i]; + } + reduceMrcStack(mrcStack); + // from now on, everything is the same as if these were the original points and scalars + points = points2; + tables = tables2; + scalars = scalars2; + windowSizes = windowSizes2; + n = n2; } - reduceMrcStack(mrcStack); - // from now on everything is the same as if these were the original points and scalars - points = points2; - tables = tables2; - scalars = scalars2; - windowSizes = windowSizes2; - n = n2; // slice scalars let scalarChunks = scalars.map((s, i) => From 6bfac4d9c6c717e8467cf690e6f4d0d82998e3c2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 15:57:25 +0100 Subject: [PATCH 0863/1215] make sure non-glv version has test coverage --- src/lib/gadgets/ecdsa.unit-test.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 144578c2cb..da11c384f5 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -14,11 +14,13 @@ import { foreignField, throwError, uniformForeignField } from './test-utils.js'; import { Second, equivalentProvable, + fromRandom, map, oneOf, record, unit, } from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; // quick tests const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); @@ -49,27 +51,39 @@ for (let Curve of curves) { } ); + // with 30% prob, test the version without GLV even if the curve supports it + let noGlv = fromRandom(Random.map(Random.fraction(), (f) => f < 0.3)); + // provable method we want to test - const verify = (s: Second) => { - Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); + const verify = (s: Second, noGlv: boolean) => { + let hasGlv = Curve.hasEndomorphism; + if (noGlv) Curve.hasEndomorphism = false; // hack to force non-GLV version + try { + Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); + } finally { + Curve.hasEndomorphism = hasGlv; + } }; // positive test - equivalentProvable({ from: [signature], to: unit })( + equivalentProvable({ from: [signature, noGlv], to: unit })( () => {}, verify, 'valid signature verifies' ); // negative test - equivalentProvable({ from: [pseudoSignature], to: unit })( + equivalentProvable({ from: [pseudoSignature, noGlv], to: unit })( () => throwError('invalid signature'), verify, 'invalid signature fails' ); // test against constant implementation, with both invalid and valid signatures - equivalentProvable({ from: [oneOf(signature, pseudoSignature)], to: unit })( + equivalentProvable({ + from: [oneOf(signature, pseudoSignature), noGlv], + to: unit, + })( ({ signature, publicKey, msg }) => { assert(verifyEcdsaConstant(Curve, signature, msg, publicKey), 'verifies'); }, From ecb8339b263cf7f494665febe75f8d21ac55d154 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 15:57:34 +0100 Subject: [PATCH 0864/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ede15f8f64..7f1fa573a8 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ede15f8f647b48487b1783389698abba22395f7e +Subproject commit 7f1fa573a87fd8934f7801ff3da984988d1a0420 From f72f57ae717640df15f153cd14f9162a10e83ad4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 16:22:37 +0100 Subject: [PATCH 0865/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7f1fa573a8..862076ec10 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7f1fa573a87fd8934f7801ff3da984988d1a0420 +Subproject commit 862076ec1079138d20257fad9cb8297f443b2a74 From 380577a25093ea9cc8eadea47917ec80c3525034 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 16:52:25 +0100 Subject: [PATCH 0866/1215] dump ecdsa vk with fewer constraints --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 9c4f4f259a..b7a582fcda 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "1025b5f3a56c5366fd44d13f2678bba563c9581c6bacfde2b82a8dd49e33f2a2", + "digest": "812837fe4422b1e0eef563d0ea4db756880d91d823bf3c718114bb252c910db", "methods": { "verifyEcdsa": { - "rows": 38823, - "digest": "65c6f9efe1069f73adf3a842398f95d2" + "rows": 30615, + "digest": "4cfbbd9ee3d6ba141b7ba24a15889969" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAPuFca4nCkavRK/kopNaYXLXQp30LgUt/V0UJqSuP4QXztIlWzZFWZ0ETZmroQESjM3Cl1C6O17KMs0QEJFJmQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgW9fGIdxPWc2TMCE7mIgqn1pcbKC1rjoMpStE7yiXTC4URGk/USFy0xf0tpXILTP7+nqebXCb+AvUnHe6cManJ146ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "2818165315298159349200200610054154136732885996642834698461943280515655745528" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAK2yyyBQxwJz7O9wzMUdV0cuqOpuEoBs0A0YH8vifHsrc8GTF0AgBm/A2wdrPQB+hHe67QVSnaDKiYoXR3TzRCQgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MApMRQAtQgtEaZkMIsQaA6SxG75uU56yZRaFSazcM+OgO41jmYrOqxZJrJwiPLcniADnWAkPb06CuoY29KAa4cJfDR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "3139301773139601589155221041968477572715505359708488668851725819716203716299" } } } \ No newline at end of file From 5945866597b93e0638ab2bf1a9bed0ea023db423 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 10:36:24 +0100 Subject: [PATCH 0867/1215] fix: struct returns empty --- src/lib/circuit_value.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index d0a85e5cc9..679b78efda 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -384,6 +384,7 @@ function Struct< }; toJSON: (x: T) => J; fromJSON: (x: J) => T; + empty: () => T; } { class Struct_ { static type = provable(type); From e30f64e5fa081e95e234ae9bcb5eb58ba90f45a5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 21:56:59 +0100 Subject: [PATCH 0868/1215] fixup --- src/lib/hash.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index c6d66d3f9d..2b1064f7a7 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -184,10 +184,6 @@ const TokenSymbolPure: ProvableExtended< }, }; class TokenSymbol extends Struct(TokenSymbolPure) { - static get empty() { - return { symbol: '', field: Field(0) }; - } - static from(symbol: string): TokenSymbol { let bytesLength = new TextEncoder().encode(symbol).length; if (bytesLength > 6) From 408fcfad57ae6d48ef27a81602e5343c25522f7b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 09:01:56 +0100 Subject: [PATCH 0869/1215] helper method --- src/lib/gadgets/foreign-field.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 75f41a7f82..036e98ec58 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -341,6 +341,13 @@ const Field3 = { return combine(toBigint3(x)); }, + /** + * Check whether a 3-tuple of Fields is constant + */ + isConstant(x: Field3) { + return x.every((x) => x.isConstant()); + }, + /** * Provable interface for `Field3 = [Field, Field, Field]`. * From 470ec77789ab220f6894fc9be31ce4559dca5ac6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 09:02:14 +0100 Subject: [PATCH 0870/1215] adapt ff class to new gadgets --- src/lib/foreign-field.ts | 290 ++++++++++----------------------------- 1 file changed, 75 insertions(+), 215 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 8d0ce8cfeb..36a0c1b041 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,28 +1,20 @@ -import { Snarky } from '../snarky.js'; -import { mod, inverse, Fp } from '../bindings/crypto/finite_field.js'; -import { Tuple } from '../bindings/lib/binable.js'; -import { - Field, - FieldConst, - FieldVar, - checkBitLength, - withMessage, -} from './field.js'; +import { ProvablePure } from '../snarky.js'; +import { mod, Fp } from '../bindings/crypto/finite_field.js'; +import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; -import { MlArray } from './ml/base.js'; +import { Tuple, TupleN } from './util/types.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { createForeignField, ForeignField }; // internal API -export { ForeignFieldVar, ForeignFieldConst, limbBits }; +export { limbBits }; const limbBits = 88n; -type MlForeignField = [_: 0, x0: F, x1: F, x2: F]; -type ForeignFieldVar = MlForeignField; -type ForeignFieldConst = MlForeignField; type ForeignField = InstanceType>; /** @@ -66,7 +58,6 @@ type ForeignField = InstanceType>; */ function createForeignField(modulus: bigint, { unsafe = false } = {}) { const p = modulus; - const pMl = ForeignFieldConst.fromBigint(p); if (p <= 0) { throw Error(`ForeignField: expected modulus to be positive, got ${p}`); @@ -81,7 +72,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { class ForeignField { static modulus = p; - value: ForeignFieldVar; + value: Field3; static #zero = new ForeignField(0); @@ -92,7 +83,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * let x = new ForeignField(5); * ``` */ - constructor(x: ForeignField | ForeignFieldVar | bigint | number | string) { + constructor(x: ForeignField | Field3 | bigint | number | string) { if (x instanceof ForeignField) { this.value = x.value; return; @@ -103,13 +94,13 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return; } // constant - this.value = ForeignFieldVar.fromBigint(mod(BigInt(x), p)); + this.value = Field3.from(mod(BigInt(x), p)); } /** * Coerce the input to a {@link ForeignField}. */ - static from(x: ForeignField | ForeignFieldVar | bigint | number | string) { + static from(x: ForeignField | Field3 | bigint | number | string) { if (x instanceof ForeignField) return x; return new ForeignField(x); } @@ -120,8 +111,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * See {@link FieldVar} to understand constants vs variables. */ isConstant() { - let [, ...limbs] = this.value; - return limbs.every(FieldVar.isConstant); + return Field3.isConstant(this.value); } /** @@ -133,27 +123,41 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * that is, in situations where the prover computes a value outside provable code. */ toConstant(): ForeignField { - let [, ...limbs] = this.value; - let constantLimbs = mapTuple(limbs, (l) => - FieldVar.constant(FieldVar.toConstant(l)) - ); - return new ForeignField([0, ...constantLimbs]); + let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); + return new ForeignField(constantLimbs); } /** * Convert this field element to a bigint. */ toBigInt() { - return ForeignFieldVar.toBigint(this.value); + return Field3.toBigint(this.value); + } + + /** + * Assert that this field element lies in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * Note: this does not ensure that the field elements is in the canonical range [0, p). + * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. + * + * We use the weaker property by default because it is cheaper to prove and sufficient for + * ensuring validity of all our non-native field arithmetic methods. + */ + assertAlmostFieldElement() { + if (this.isConstant()) return; + // TODO + throw Error('unimplemented'); } /** * Assert that this field element lies in the range [0, p), * where p is the foreign field modulus. */ - assertValidElement() { + assertCanonicalFieldElement() { if (this.isConstant()) return; - Snarky.foreignField.assertValidElement(this.value, pMl); + // TODO + throw Error('unimplemented'); } // arithmetic with full constraints, for safe use @@ -213,19 +217,9 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * */ static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { - if (xs.every(isConstant)) { - let sum = xs.reduce((sum: bigint, x, i): bigint => { - if (i === 0) return toFp(x); - return sum + BigInt(operations[i - 1]) * toFp(x); - }, 0n); - // note: we don't reduce mod p because the constructor does that - return new ForeignField(sum); - } - let fields = MlArray.to(xs.map(toVar)); - let opModes = MlArray.to( - operations.map((op) => (op === 1 ? OpMode.Add : OpMode.Sub)) - ); - let z = Snarky.foreignField.sumChain(fields, opModes, pMl); + let fields = xs.map(toLimbs); + let ops = operations.map((op) => (op === 1 ? 1n : -1n)); + let z = Gadgets.ForeignField.sum(fields, ops, p); return new ForeignField(z); } @@ -237,11 +231,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ``` */ mul(y: ForeignField | bigint | number) { - if (this.isConstant() && isConstant(y)) { - let z = mod(this.toBigInt() * toFp(y), p); - return new ForeignField(z); - } - let z = Snarky.foreignField.mul(this.value, toVar(y), pMl); + let z = Gadgets.ForeignField.mul(this.value, toLimbs(y), p); return new ForeignField(z); } @@ -254,29 +244,8 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ``` */ inv(): ForeignField { - if (this.isConstant()) { - let z = inverse(this.toBigInt(), p); - if (z === undefined) { - if (this.toBigInt() === 0n) { - throw Error('ForeignField.inv(): division by zero'); - } else { - // in case this is used with non-prime moduli - throw Error('ForeignField.inv(): inverse does not exist'); - } - } - return new ForeignField(z); - } - let z = Provable.witness(ForeignField, () => this.toConstant().inv()); - - // in unsafe mode, `witness` didn't constrain z to be a valid field element - if (unsafe) z.assertValidElement(); - - // check that x * z === 1 - // TODO: range checks added by `mul` on `one` are unnecessary, since we already assert that `one` equals 1 - let one = Snarky.foreignField.mul(this.value, z.value, pMl); - new ForeignField(one).assertEquals(new ForeignField(1)); - - return z; + let z = Gadgets.ForeignField.inv(this.value, p); + return new ForeignField(z); } // convenience methods @@ -297,7 +266,11 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); } } - return Provable.assertEqual(ForeignField, this, ForeignField.from(y)); + return Provable.assertEqual( + ForeignField.provable, + this, + ForeignField.from(y) + ); } catch (err) { throw withMessage(err, message); } @@ -314,7 +287,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() === toFp(y)); } - return Provable.equal(ForeignField, this, ForeignField.from(y)); + return Provable.equal(ForeignField.provable, this, ForeignField.from(y)); } // bit packing @@ -326,7 +299,7 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { */ toBits(length = sizeInBits) { checkBitLength('ForeignField.toBits()', length, sizeInBits); - let [l0, l1, l2] = this.toFields(); + let [l0, l1, l2] = this.value; let limbSize = Number(limbBits); let xBits = l0.toBits(Math.min(length, limbSize)); length -= limbSize; @@ -350,64 +323,42 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { 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)); - let x = ForeignField.fromFields([l0, l1, l2]); - // TODO: can this be made more efficient? since the limbs are already range-checked - if (length === sizeInBits) x.assertValidElement(); - return x; + // note: due to the check on the number of bits, we know we return an "almost valid" field element + return ForeignField.from([l0, l1, l2]); } // Provable /** - * `Provable.toFields`, see {@link Provable.toFields} + * `Provable`, see {@link Provable} */ - static toFields(x: ForeignField) { - let [, ...limbs] = x.value; - return limbs.map((x) => new Field(x)); - } + static provable: ProvablePure = { + toFields(x) { + return x.value; + }, + toAuxiliary(): [] { + return []; + }, + sizeInFields() { + return 3; + }, + fromFields(fields) { + let limbs = TupleN.fromArray(3, fields); + return new ForeignField(limbs); + }, + /** + * This performs the check in {@link ForeignField.assertAlmostFieldElement}. + */ + check(x: ForeignField) { + x.assertAlmostFieldElement(); + }, + }; /** * Instance version of `Provable.toFields`, see {@link Provable.toFields} */ - toFields() { - return ForeignField.toFields(this); - } - - /** - * `Provable.toAuxiliary`, see {@link Provable.toAuxiliary} - */ - static toAuxiliary(): [] { - return []; - } - /** - * `Provable.sizeInFields`, see {@link Provable.sizeInFields} - */ - static sizeInFields() { - return 3; - } - - /** - * `Provable.fromFields`, see {@link Provable.fromFields} - */ - static fromFields(fields: Field[]) { - let fieldVars = fields.map((x) => x.value); - let limbs = arrayToTuple(fieldVars, 3, 'ForeignField.fromFields()'); - return new ForeignField([0, ...limbs]); - } - - /** - * `Provable.check`, see {@link Provable.check} - * - * This will check that the field element is in the range [0, p), - * where p is the foreign field modulus. - * - * **Exception**: If {@link createForeignField} is called with `{ unsafe: true }`, - * we don't check that field elements are valid by default. - */ - static check(x: ForeignField) { - // if the `unsafe` flag is set, we don't add any constraints when creating a new variable - // this means a user has to take care of proper constraining themselves - if (!unsafe) x.assertValidElement(); + toFields(): Field[] { + return this.value; } } @@ -415,9 +366,9 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { if (x instanceof ForeignField) return x.toBigInt(); return mod(BigInt(x), p); } - function toVar(x: bigint | number | string | ForeignField): ForeignFieldVar { + function toLimbs(x: bigint | number | string | ForeignField): Field3 { if (x instanceof ForeignField) return x.value; - return ForeignFieldVar.fromBigint(mod(BigInt(x), p)); + return Field3.from(mod(BigInt(x), p)); } function isConstant(x: bigint | number | string | ForeignField) { if (x instanceof ForeignField) return x.isConstant(); @@ -427,100 +378,9 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { return ForeignField; } -enum OpMode { - Add, - Sub, -} - -// helpers - -const limbMax = (1n << limbBits) - 1n; - // the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus // see RFC: https://github.com/o1-labs/proof-systems/blob/1fdb1fd1d112f9d4ee095dbb31f008deeb8150b0/book/src/rfcs/foreign_field_mul.md // since p = 2^254 + eps for both Pasta fields with eps small, a fairly tight lower bound is // f_max >= sqrt(2^254 * 2^264) = 2^259 const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * limbBits) / 2n; const foreignFieldMax = 1n << foreignFieldMaxBits; - -const ForeignFieldConst = { - fromBigint(x: bigint): ForeignFieldConst { - let limbs = mapTuple(to3Limbs(x), FieldConst.fromBigint); - return [0, ...limbs]; - }, - toBigint([, ...limbs]: ForeignFieldConst): bigint { - return from3Limbs(mapTuple(limbs, FieldConst.toBigint)); - }, - [0]: [ - 0, - FieldConst[0], - FieldConst[0], - FieldConst[0], - ] satisfies ForeignFieldConst, - [1]: [ - 0, - FieldConst[1], - FieldConst[0], - FieldConst[0], - ] satisfies ForeignFieldConst, -}; - -const ForeignFieldVar = { - fromBigint(x: bigint): ForeignFieldVar { - let limbs = mapTuple(to3Limbs(x), FieldVar.constant); - return [0, ...limbs]; - }, - toBigint([, ...limbs]: ForeignFieldVar): bigint { - return from3Limbs(mapTuple(limbs, FieldVar.toBigint)); - }, - [0]: [0, FieldVar[0], FieldVar[0], FieldVar[0]] satisfies ForeignFieldVar, - [1]: [0, FieldVar[1], FieldVar[0], FieldVar[0]] satisfies ForeignFieldVar, -}; - -function to3Limbs(x: bigint): [bigint, bigint, bigint] { - let l0 = x & limbMax; - x >>= limbBits; - let l1 = x & limbMax; - let l2 = x >> limbBits; - return [l0, l1, l2]; -} - -function from3Limbs(limbs: [bigint, bigint, bigint]): bigint { - let [l0, l1, l2] = limbs; - return l0 + ((l1 + (l2 << limbBits)) << limbBits); -} - -function mapTuple, B>( - tuple: T, - f: (a: T[number]) => B -): { [i in keyof T]: B } { - return tuple.map(f) as any; -} - -/** - * tuple type that has the length as generic parameter - */ -type TupleN = N extends N - ? number extends N - ? T[] - : _TupleOf - : never; -type _TupleOf = R['length'] extends N - ? R - : _TupleOf; - -/** - * Type-safe way of converting an array to a fixed-length tuple (same JS representation, but different TS type) - */ -function arrayToTuple( - arr: E[], - size: N, - name: string -): TupleN { - if (arr.length !== size) { - throw Error( - `${name}: expected array of length ${size}, got length ${arr.length}` - ); - } - return arr as any; -} From f6e43a283df2c781cc8489ccc1766c6fcbc1bd03 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 09:56:07 +0100 Subject: [PATCH 0871/1215] Update src/lib/gadgets/gadgets.ts Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b0b06dabba..9f16d3f7f0 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -71,7 +71,7 @@ const Gadgets = { * ``` * * **Note**: Small "negative" field element inputs are interpreted as large integers close to the field size, - * and don't pass the 32-bit check. If you want to prove that a value lies in the int64 range [-2^31, 2^31), + * and don't pass the 32-bit check. If you want to prove that a value lies in the int32 range [-2^31, 2^31), * you could use `rangeCheck32(x.add(1n << 31n))`. */ rangeCheck32(x: Field) { From 89d806b593675ef99aaa60ea6ee506dbaefac76b Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 09:56:35 +0100 Subject: [PATCH 0872/1215] Update src/lib/gadgets/gadgets.ts Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 9f16d3f7f0..3f011a901b 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -575,9 +575,9 @@ const Gadgets = { */ Field3, /** - * Division modulo 2^32. The operation decomposes a {@link Field} element in the range [0, 2^64] into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. + * Division modulo 2^32. The operation decomposes a {@link Field} element in the range [0, 2^64) into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. * - * **Note:** The Gadget expects the input to be in the range [0, 2^64]. If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution. + * **Note:** The gadget acts as a proof that the input is in the range [0, 2^64). If the input exceeds 64 bits, the gadget fails. * * Asserts that both `remainder` and `quotient` are in the range [0, 2^32) using {@link Gadgets.rangeCheck32}. * From 72d38b4523d87bec6d4d91c0f6eab5fd50154c3c Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 09:57:02 +0100 Subject: [PATCH 0873/1215] Update src/lib/gadgets/gadgets.ts Co-authored-by: Gregor Mitscha-Baude --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 3f011a901b..71860d5240 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -599,7 +599,7 @@ const Gadgets = { * * It uses {@link Gadgets.divMod32} internally by adding the two {@link Field} elements and then decomposing the result into `remainder` and `quotient` and returning the `remainder`. * - * **Note:** The Gadget expects the input to be in the range [0, 2^64]. If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution. + * **Note:** The gadget assumes both inputs to be in the range [0, 2^64). When called with non-range-checked inputs, be aware that the sum `a + b` can overflow the native field and the gadget can succeed but return an invalid result. * * @example * ```ts From 84f08238461c4097fa93e545986841b60d84ab95 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 10:08:56 +0100 Subject: [PATCH 0874/1215] address feedback --- src/lib/gadgets/arithmetic.ts | 8 ++--- src/lib/gadgets/arithmetic.unit-test.ts | 4 +-- src/lib/int.ts | 46 ++++--------------------- 3 files changed, 12 insertions(+), 46 deletions(-) diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 351ddb0af1..414ddfe814 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -14,8 +14,8 @@ function divMod32(n: Field) { ); let nBigInt = n.toBigInt(); - let q = nBigInt / (1n << 32n); - let r = nBigInt - q * (1n << 32n); + let q = nBigInt >> 32n; + let r = nBigInt - (q << 32n); return { remainder: new Field(r), quotient: new Field(q), @@ -26,8 +26,8 @@ function divMod32(n: Field) { provableTuple([Field, Field]), () => { let nBigInt = n.toBigInt(); - let q = nBigInt / (1n << 32n); - let r = nBigInt - q * (1n << 32n); + let q = nBigInt >> 32n; + let r = nBigInt - (q << 32n); return [new Field(q), new Field(r)]; } ); diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts index e1ca27bd5a..cf1398dadf 100644 --- a/src/lib/gadgets/arithmetic.unit-test.ts +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -32,8 +32,8 @@ let Arithmetic = ZkProgram({ await Arithmetic.compile(); const divMod32Helper = (x: bigint) => { - let quotient = x / (1n << 32n); - let remainder = x - quotient * (1n << 32n); + let quotient = x >> 32n; + let remainder = x - (quotient << 32n); return { remainder, quotient }; }; const divMod32Output = record({ remainder: field, quotient: field }); diff --git a/src/lib/int.ts b/src/lib/int.ts index a04ad02a92..683cf7abc1 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -239,13 +239,7 @@ class UInt64 extends CircuitValue { * corresponding bit of the operand is `0`, and returning `0` if the * corresponding bit of the operand is `1`. * - * - * NOT is implemented in two different ways. If the `checked` parameter is set to `true` - * the {@link Gadgets.xor} gadget is reused with a second argument to be an - * all one bitmask the same length. This approach needs as many rows as an XOR would need - * for a single negation. If the `checked` parameter is set to `false`, NOT is - * implemented as a subtraction of the input from the all one bitmask. This - * implementation is returned by default if no `checked` parameter is provided. + * NOT is implemented as a subtraction of the input from the all one bitmask * * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) * @@ -258,23 +252,13 @@ class UInt64 extends CircuitValue { * console.log(b.toBigInt().toString(2)); * // 1111111111111111111111111111111111111111111111111111111111111010 * - * // NOTing 4 bits with the checked version utilizing the xor gadget - * let a = UInt64.from(0b0101); - * let b = a.not(); - * - * console.log(b.toBigInt().toString(2)); - * // 1111111111111111111111111111111111111111111111111111111111111010 - * * ``` * * @param a - The value to apply NOT to. - * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it - * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented - * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. * */ - not(checked = true) { - return Gadgets.not(this.value, UInt64.NUM_BITS, checked); + not() { + return Gadgets.not(this.value, UInt64.NUM_BITS, false); } /** @@ -765,13 +749,7 @@ class UInt32 extends CircuitValue { * corresponding bit of the operand is `0`, and returning `0` if the * corresponding bit of the operand is `1`. * - * - * NOT is implemented in two different ways. If the `checked` parameter is set to `true` - * the {@link Gadgets.xor} gadget is reused with a second argument to be an - * all one bitmask the same length. This approach needs as many rows as an XOR would need - * for a single negation. If the `checked` parameter is set to `false`, NOT is - * implemented as a subtraction of the input from the all one bitmask. This - * implementation is returned by default if no `checked` parameter is provided. + * NOT is implemented as a subtraction of the input from the all one bitmask. * * You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#not) * @@ -779,28 +757,16 @@ class UInt32 extends CircuitValue { * ```ts * // NOTing 4 bits with the unchecked version * let a = UInt32.from(0b0101); - * let b = a.not(false); - * - * console.log(b.toBigInt().toString(2)); - * // 11111111111111111111111111111010 - * - * // NOTing 4 bits with the checked version utilizing the xor gadget - * let a = UInt32.from(0b0101); * let b = a.not(); * * console.log(b.toBigInt().toString(2)); * // 11111111111111111111111111111010 - * * ``` * * @param a - The value to apply NOT to. - * @param checked - Optional boolean to determine if the checked or unchecked not implementation is used. If it - * is set to `true` the {@link Gadgets.xor} gadget is reused. If it is set to `false`, NOT is implemented - * as a subtraction of the input from the all one bitmask. It is set to `false` by default if no parameter is provided. - * */ - not(checked = true) { - return Gadgets.not(this.value, UInt32.NUM_BITS, checked); + not() { + return Gadgets.not(this.value, UInt32.NUM_BITS, false); } /** From cf16482338dadb4683385ad39c9454c7f64c6489 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 10:11:41 +0100 Subject: [PATCH 0875/1215] fix changelog --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa3d03bfd..2c9cb359e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,18 +25,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- `Gadgets.rotate32` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 -- `Gadgets.divMod32` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 -- `Gadgets.rangeCheck32` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 -- `Gadgets.rangeCheckHelper` for returning a new Field element of the first `length` bits of an element https://github.com/o1-labs/o1js/pull/1259 -- `Gadgets.addMod32` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rangeCheck32()` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.rangeCheckHelper()` for returning a new Field element of the first `length` bits of an element https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.addMod32()` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 - Expose new bitwise gadgets on `UInt32` and `UInt64` https://github.com/o1-labs/o1js/pull/1259 - - bitwise XOR via `{UInt32, UInt64}.xor` - - bitwise NOT via `{UInt32, UInt64}.not` - - bitwise ROTATE via `{UInt32, UInt64}.rotate` - - bitwise LEFTSHIFT via `{UInt32, UInt64}.leftShift` - - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift` - - bitwise AND via `{UInt32, UInt64}.and` + - bitwise XOR via `{UInt32, UInt64}.xor()` + - bitwise NOT via `{UInt32, UInt64}.not()` + - bitwise ROTATE via `{UInt32, UInt64}.rotate()` + - bitwise LEFTSHIFT via `{UInt32, UInt64}.leftShift()` + - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift()` + - bitwise AND via `{UInt32, UInt64}.and()` ### Changed From 2042e0230f577ebae6471ecdbee167a10aa49370 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 10:38:51 +0100 Subject: [PATCH 0876/1215] add rangeCheckN and isInRangeN --- CHANGELOG.md | 2 -- src/lib/field.ts | 3 -- src/lib/gadgets/gadgets.ts | 49 +++++++++++++++++++++----- src/lib/gadgets/range-check.ts | 62 +++++++++++++++++++++------------ src/lib/hash.ts | 3 +- src/lib/int.ts | 63 +++++++++------------------------- src/lib/string.ts | 2 +- 7 files changed, 98 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c9cb359e3..9756c0ec95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.rangeCheck32()` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 -- `Gadgets.rangeCheckHelper()` for returning a new Field element of the first `length` bits of an element https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.addMod32()` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 - Expose new bitwise gadgets on `UInt32` and `UInt64` https://github.com/o1-labs/o1js/pull/1259 - bitwise XOR via `{UInt32, UInt64}.xor()` @@ -49,7 +48,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.account.x.assertNothing()` is now `this.account.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1265 - `this.currentSlot.assertBetween(x,y)` is now `this.currentSlot.requireBetween(x,y)` https://github.com/o1-labs/o1js/pull/1265 - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 -- Deprecate `field.rangeCheckHelper` in favor of `Gadgets.rangeCheckHelper` https://github.com/o1-labs/o1js/pull/1259 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) diff --git a/src/lib/field.ts b/src/lib/field.ts index 0b1e0dba80..6df164143b 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -997,9 +997,6 @@ class Field { } /** - * - * @deprecated use `Gadgets.rangeCheckHelper` instead. - * * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. * * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 71860d5240..b425bc7068 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -6,7 +6,8 @@ import { multiRangeCheck, rangeCheck64, rangeCheck32, - rangeCheckHelper, + rangeCheckN, + isInRangeN, } from './range-check.js'; import { not, @@ -79,20 +80,50 @@ const Gadgets = { }, /** - * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. + * Asserts that the input value is in the range [0, 2^n). `n` must be a multiple of 16. * - * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. + * This function proves that the provided field element can be represented with `n` bits. + * If the field element exceeds `n` bits, an error is thrown. * - * As {@link Field} elements are represented using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness), - * the resulting {@link Field} element will equal the original one if it fits in `length` bits. + * @param x - The value to be range-checked. + * @param n - The number of bits to be considered for the range check. + * + * @throws Throws an error if the input value exceeds `n` bits. * - * @param length - The number of bits to take from this {@link Field} element. + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * Gadgets.rangeCheck(32, x); // successfully proves 32-bit range * - * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * Gadgets.rangeCheck(32, xLarge); // throws an error since input exceeds 32 bits + * ``` */ - rangeCheckHelper(length: number, x: Field) { - return rangeCheckHelper(length, x); + rangeCheckN(n: number, x: Field) { + return rangeCheckN(n, x); }, + + /** + * Checks whether the input value is in the range [0, 2^n). `n` must be a multiple of 16. + * + * This function proves that the provided field element can be represented with `n` bits. + * If the field element exceeds `n` bits, an error is thrown. + * + * @param x - The value to be range-checked. + * @param n - The number of bits to be considered for the range check. + * + * @returns a Bool indicating whether the input value is in the range [0, 2^n). + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(12345678n)); + * let inRange = Gadgets.isInRangeN(32, x); // return Bool(true) + * ``` + */ + isInRangeN(n: number, x: Field) { + return isInRangeN(n, x); + }, + /** * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 2854be5053..5902495b54 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -11,6 +11,8 @@ export { multiRangeCheck, compactMultiRangeCheck, rangeCheckHelper, + rangeCheckN, + isInRangeN, }; export { l, l2, l3, lMask, l2Mask }; @@ -29,28 +31,6 @@ function rangeCheck32(x: Field) { actual.assertEquals(x); } -/** - * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. - */ -function rangeCheckHelper(length: number, x: Field) { - assert( - length <= Fp.sizeInBits, - `bit length must be ${Fp.sizeInBits} or less, got ${length}` - ); - assert(length > 0, `bit length must be positive, got ${length}`); - assert(length % 16 === 0, '`length` has to be a multiple of 16.'); - - let lengthDiv16 = length / 16; - if (x.isConstant()) { - let bits = FieldProvable.toBits(x.toBigInt()) - .slice(0, length) - .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(FieldProvable.fromBits(bits)); - } - let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); - return new Field(y); -} - /** * Asserts that x is in the range [0, 2^64) */ @@ -253,3 +233,41 @@ function rangeCheck1Helper(inputs: { [z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0] ); } + +/** + * Helper function that creates a new {@link Field} element from the first `length` bits of this {@link Field} element. + */ +function rangeCheckHelper(length: number, x: Field) { + assert( + length <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + assert(length > 0, `bit length must be positive, got ${length}`); + assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + + let lengthDiv16 = length / 16; + if (x.isConstant()) { + let bits = FieldProvable.toBits(x.toBigInt()) + .slice(0, length) + .concat(Array(Fp.sizeInBits - length).fill(false)); + return new Field(FieldProvable.fromBits(bits)); + } + let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); + return new Field(y); +} + +/** + * Asserts that x is in the range [0, 2^n) + */ +function rangeCheckN(n: number, x: Field) { + let actual = rangeCheckHelper(n, x); + actual.assertEquals(x); +} + +/** + * Checks that x is in the range [0, 2^n) and returns a Boolean indicating whether the check passed. + */ +function isInRangeN(n: number, x: Field) { + let actual = rangeCheckHelper(n, x); + return actual.equals(x); +} diff --git a/src/lib/hash.ts b/src/lib/hash.ts index ea8f2ca212..d23518ef24 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -167,8 +167,7 @@ const TokenSymbolPure: ProvableExtended< return 1; }, check({ field }: TokenSymbol) { - let actual = Gadgets.rangeCheckHelper(48, field); - actual.assertEquals(field); + Gadgets.rangeCheckN(48, field); }, toJSON({ symbol }) { return symbol; diff --git a/src/lib/int.ts b/src/lib/int.ts index 683cf7abc1..15d83e4356 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -67,8 +67,7 @@ class UInt64 extends CircuitValue { } static check(x: UInt64) { - let actual = Gadgets.rangeCheckHelper(UInt64.NUM_BITS, x.value); - actual.assertEquals(x.value); + Gadgets.rangeCheckN(UInt64.NUM_BITS, x.value); } static toInput(x: UInt64): HashInput { @@ -143,11 +142,11 @@ class UInt64 extends CircuitValue { () => new Field(x.toBigInt() / y_.toBigInt()) ); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, q).assertEquals(q); + Gadgets.rangeCheckN(UInt64.NUM_BITS, q); // TODO: Could be a bit more efficient let r = x.sub(q.mul(y_)).seal(); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, r).assertEquals(r); + Gadgets.rangeCheckN(UInt64.NUM_BITS, r); let r_ = new UInt64(r); let q_ = new UInt64(q); @@ -183,7 +182,7 @@ class UInt64 extends CircuitValue { */ mul(y: UInt64 | number) { let z = this.value.mul(UInt64.from(y).value); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); + Gadgets.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -192,7 +191,7 @@ class UInt64 extends CircuitValue { */ add(y: UInt64 | number) { let z = this.value.add(UInt64.from(y).value); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); + Gadgets.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -201,7 +200,7 @@ class UInt64 extends CircuitValue { */ sub(y: UInt64 | number) { let z = this.value.sub(UInt64.from(y).value); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, z).assertEquals(z); + Gadgets.rangeCheckN(UInt64.NUM_BITS, z); return new UInt64(z); } @@ -376,15 +375,9 @@ class UInt64 extends CircuitValue { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.rangeCheckHelper( - UInt64.NUM_BITS, - xMinusY - ).equals(xMinusY); + let xMinusYFits = Gadgets.isInRangeN(UInt64.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.rangeCheckHelper( - UInt64.NUM_BITS, - yMinusX - ).equals(yMinusX); + let yMinusXFits = Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits @@ -402,15 +395,9 @@ class UInt64 extends CircuitValue { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.rangeCheckHelper( - UInt64.NUM_BITS, - xMinusY - ).equals(xMinusY); + let xMinusYFits = Gadgets.isInRangeN(UInt64.NUM_BITS, xMinusY); - let yMinusXFits = Gadgets.rangeCheckHelper( - UInt64.NUM_BITS, - yMinusX - ).equals(yMinusX); + let yMinusXFits = Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits @@ -441,10 +428,7 @@ class UInt64 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.rangeCheckHelper(UInt64.NUM_BITS, yMinusX).assertEquals( - yMinusX, - message - ); + Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX).assertTrue(message); } /** @@ -883,14 +867,8 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.rangeCheckHelper( - UInt32.NUM_BITS, - xMinusY - ).equals(xMinusY); - let yMinusXFits = Gadgets.rangeCheckHelper( - UInt32.NUM_BITS, - yMinusX - ).equals(yMinusX); + let xMinusYFits = Gadgets.isInRangeN(UInt32.NUM_BITS, xMinusY); + let yMinusXFits = Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -906,14 +884,8 @@ class UInt32 extends CircuitValue { } else { let xMinusY = this.value.sub(y.value).seal(); let yMinusX = xMinusY.neg(); - let xMinusYFits = Gadgets.rangeCheckHelper( - UInt32.NUM_BITS, - xMinusY - ).equals(xMinusY); - let yMinusXFits = Gadgets.rangeCheckHelper( - UInt32.NUM_BITS, - yMinusX - ).equals(yMinusX); + let xMinusYFits = Gadgets.isInRangeN(UInt32.NUM_BITS, xMinusY); + let yMinusXFits = Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX); xMinusYFits.or(yMinusXFits).assertEquals(true); // x <= y if y - x fits in 64 bits return yMinusXFits; @@ -943,10 +915,7 @@ class UInt32 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.rangeCheckHelper(UInt32.NUM_BITS, yMinusX).assertEquals( - yMinusX, - message - ); + Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX).assertTrue(message); } /** diff --git a/src/lib/string.ts b/src/lib/string.ts index 031bbf51f8..8045dd3318 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -32,7 +32,7 @@ class Character extends CircuitValue { // TODO: Add support for more character sets // right now it's 16 bits because 8 not supported :/ static check(c: Character) { - Gadgets.rangeCheckHelper(16, c.value).assertEquals(c.value); + Gadgets.rangeCheckN(16, c.value); } } From 86b36291c42ba87b972f8f805745b726c2927683 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 24 Nov 2023 09:28:24 +0100 Subject: [PATCH 0877/1215] add helper to apply all ff range checks --- src/lib/foreign-field.ts | 11 +++++------ src/lib/gadgets/foreign-field.ts | 28 +++++++++++++++++++++++++++- src/lib/gadgets/gadgets.ts | 30 ++++++++++++++++++++++++++++++ src/lib/util/types.ts | 4 ++++ 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 36a0c1b041..3230568f9a 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -53,10 +53,8 @@ type ForeignField = InstanceType>; * ``` * * @param modulus the modulus of the finite field you are instantiating - * @param options - * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. */ -function createForeignField(modulus: bigint, { unsafe = false } = {}) { +function createForeignField(modulus: bigint) { const p = modulus; if (p <= 0) { @@ -145,9 +143,10 @@ function createForeignField(modulus: bigint, { unsafe = false } = {}) { * ensuring validity of all our non-native field arithmetic methods. */ assertAlmostFieldElement() { - if (this.isConstant()) return; - // TODO - throw Error('unimplemented'); + // TODO: this is not very efficient, but the only way to abstract away the complicated + // range check assumptions and also not introduce a global context of pending range checks. + // we plan to get rid of bounds checks anyway, then this is just a multi-range check + Gadgets.ForeignField.assertAlmostFieldElements([this.value], p); } /** diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 036e98ec58..bffa7b6dad 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,7 +5,7 @@ import { import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Tuple } from '../util/types.js'; +import { Tuple, TupleN } from '../util/types.js'; import { assert, bitSlice, exists, toVars } from './common.js'; import { l, @@ -38,6 +38,8 @@ const ForeignField = { mul: multiply, inv: inverse, div: divide, + + assertAlmostFieldElements, }; /** @@ -326,6 +328,30 @@ function weakBound(x: Field, f: bigint) { return x.add(lMask - (f >> l2)); } +/** + * Apply range checks and weak bounds checks to a list of Field3s. + * Optimal if the list length is a multiple of 3. + */ +function assertAlmostFieldElements(xs: Field3[], f: bigint) { + let bounds: Field[] = []; + + for (let x of xs) { + multiRangeCheck(x); + + bounds.push(weakBound(x[2], f)); + if (TupleN.hasLength(3, bounds)) { + multiRangeCheck(bounds); + bounds = []; + } + } + if (TupleN.hasLength(1, bounds)) { + multiRangeCheck([...bounds, Field.from(0n), Field.from(0n)]); + } + if (TupleN.hasLength(2, bounds)) { + multiRangeCheck([...bounds, Field.from(0n)]); + } +} + const Field3 = { /** * Turn a bigint into a 3-tuple of Fields diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b67ef58fd9..fc2b45ac90 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -474,6 +474,36 @@ const Gadgets = { div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); }, + + /** + * Prove that each of the given {@link Field3} elements is "almost" reduced modulo f, + * i.e., satisfies the assumptions required by {@link ForeignField.mul} and other gadgets: + * - each limb is in the range [0, 2^88) + * - the most significant limb is less or equal than the modulus, x[2] <= f[2] + * + * **Note**: This method is most efficient when the number of input elements is a multiple of 3. + * + * @throws if any of the assumptions is violated. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(4n)); + * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); + * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); + * + * ForeignField.assertAlmostFieldElements([x, y, z], f); + * + * // now we can use x, y, z as inputs to foreign field multiplication + * let xy = ForeignField.mul(x, y, f); + * let xyz = ForeignField.mul(xy, z, f); + * + * // since xy is an input to another multiplication, we need to prove that it is almost reduced again! + * ForeignField.assertAlmostFieldElements([xy], f); // TODO: would be more efficient to batch this with 2 other elements + * ``` + */ + assertAlmostFieldElements(xs: Field3[], f: bigint) { + ForeignField.assertAlmostFieldElements(xs, f); + }, }, /** diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index 201824ec48..f5fa8fcc10 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -37,6 +37,10 @@ const TupleN = { ); return arr as any; }, + + hasLength(n: N, tuple: T[]): tuple is TupleN { + return tuple.length === n; + }, }; type TupleRec = R['length'] extends N From b97c7175490c24d2f09bf3d43112d29adbf59e46 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 10:46:25 +0100 Subject: [PATCH 0878/1215] add correctly typed constructor for UInt32 and UInt64 --- src/lib/int.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 15d83e4356..5abb55c116 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -4,6 +4,7 @@ import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; import { Gadgets } from './gadgets/gadgets.js'; +import { FILE } from 'dns'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -15,6 +16,12 @@ class UInt64 extends CircuitValue { @prop value: Field; static NUM_BITS = 64; + constructor(x: UInt64 | UInt32 | Field | number | string | bigint) { + if (x instanceof UInt64 || x instanceof UInt32) x = x.value; + else if (!(x instanceof Field)) x = Field(x); + super(UInt64.checkConstant(x)); + } + /** * Static method to create a {@link UInt64} with value `0`. */ @@ -536,6 +543,12 @@ class UInt32 extends CircuitValue { @prop value: Field; static NUM_BITS = 32; + constructor(x: UInt32 | Field | number | string | bigint) { + if (x instanceof UInt32) x = x.value; + else if (!(x instanceof Field)) x = Field(x); + super(UInt32.checkConstant(x)); + } + /** * Static method to create a {@link UInt32} with value `0`. */ @@ -608,6 +621,7 @@ class UInt32 extends CircuitValue { if (x instanceof UInt32) x = x.value; return new this(this.checkConstant(Field(x))); } + /** * Creates a {@link UInt32} with a value of 4,294,967,295. */ From 973a3662c79f9c7948018b4dd13e80a95d349c84 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 11:02:22 +0100 Subject: [PATCH 0879/1215] fix vk tests --- src/lib/gadgets/gadgets.ts | 5 +++-- src/lib/gadgets/range-check.ts | 4 ++-- src/lib/int.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b425bc7068..0a29968d87 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -87,6 +87,7 @@ const Gadgets = { * * @param x - The value to be range-checked. * @param n - The number of bits to be considered for the range check. + * @param message - Optional message to be displayed when the range check fails. * * @throws Throws an error if the input value exceeds `n` bits. * @@ -99,8 +100,8 @@ const Gadgets = { * Gadgets.rangeCheck(32, xLarge); // throws an error since input exceeds 32 bits * ``` */ - rangeCheckN(n: number, x: Field) { - return rangeCheckN(n, x); + rangeCheckN(n: number, x: Field, message?: string) { + return rangeCheckN(n, x, message); }, /** diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 5902495b54..273b86b4a7 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -259,9 +259,9 @@ function rangeCheckHelper(length: number, x: Field) { /** * Asserts that x is in the range [0, 2^n) */ -function rangeCheckN(n: number, x: Field) { +function rangeCheckN(n: number, x: Field, message?: string) { let actual = rangeCheckHelper(n, x); - actual.assertEquals(x); + actual.assertEquals(x, message); } /** diff --git a/src/lib/int.ts b/src/lib/int.ts index 5abb55c116..fa0aa6b7dc 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -435,7 +435,7 @@ class UInt64 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.isInRangeN(UInt64.NUM_BITS, yMinusX).assertTrue(message); + Gadgets.rangeCheckN(UInt64.NUM_BITS, yMinusX, message); } /** @@ -929,7 +929,7 @@ class UInt32 extends CircuitValue { return; } let yMinusX = y.value.sub(this.value).seal(); - Gadgets.isInRangeN(UInt32.NUM_BITS, yMinusX).assertTrue(message); + Gadgets.rangeCheckN(UInt32.NUM_BITS, yMinusX, message); } /** From 1165c5fe0ebd685253b688ec85ad1658fb3db9b2 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 11:03:02 +0100 Subject: [PATCH 0880/1215] remove constant check for now --- src/lib/int.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index fa0aa6b7dc..830b5f34d4 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -19,7 +19,7 @@ class UInt64 extends CircuitValue { constructor(x: UInt64 | UInt32 | Field | number | string | bigint) { if (x instanceof UInt64 || x instanceof UInt32) x = x.value; else if (!(x instanceof Field)) x = Field(x); - super(UInt64.checkConstant(x)); + super(x); } /** @@ -546,7 +546,7 @@ class UInt32 extends CircuitValue { constructor(x: UInt32 | Field | number | string | bigint) { if (x instanceof UInt32) x = x.value; else if (!(x instanceof Field)) x = Field(x); - super(UInt32.checkConstant(x)); + super(x); } /** From 367de21a56907e9b1f820816087b572c97dc7131 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 11:29:44 +0100 Subject: [PATCH 0881/1215] add canonical check and negate, assertLessThan --- src/lib/foreign-field.ts | 36 +++++++++++++++++++++++++------- src/lib/gadgets/foreign-field.ts | 35 +++++++++++++++++++++++++++++++ src/lib/gadgets/gadgets.ts | 27 ++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 3230568f9a..8efa4fc0ca 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -86,7 +86,7 @@ function createForeignField(modulus: bigint) { this.value = x.value; return; } - // ForeignFieldVar + // Field3 if (Array.isArray(x)) { this.value = x; return; @@ -136,6 +136,9 @@ function createForeignField(modulus: bigint) { * Assert that this field element lies in the range [0, 2^k), * where k = ceil(log2(p)) and p is the foreign field modulus. * + * **Warning**: This check is added to all `ForeignField` elements by default. + * You don't have to use it. + * * Note: this does not ensure that the field elements is in the canonical range [0, p). * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. * @@ -150,13 +153,11 @@ function createForeignField(modulus: bigint) { } /** - * Assert that this field element lies in the range [0, p), - * where p is the foreign field modulus. + * Assert that this field element is fully reduced, + * i.e. lies in the range [0, p), where p is the foreign field modulus. */ assertCanonicalFieldElement() { - if (this.isConstant()) return; - // TODO - throw Error('unimplemented'); + Gadgets.ForeignField.assertLessThan(this.value, p); } // arithmetic with full constraints, for safe use @@ -264,6 +265,7 @@ function createForeignField(modulus: bigint) { if (x !== y0) { throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); } + return; } return Provable.assertEqual( ForeignField.provable, @@ -275,6 +277,21 @@ function createForeignField(modulus: bigint) { } } + /** + * Assert that this field element is less than a constant c: `x < c`. + * @example + * ```ts + * x.assertLessThan(10); + * ``` + */ + assertLessThan(y: bigint | number, message?: string) { + try { + Gadgets.ForeignField.assertLessThan(this.value, toBigInt(y)); + } catch (err) { + throw withMessage(err, message); + } + } + /** * Check equality with a ForeignField-like value * @example @@ -361,9 +378,12 @@ function createForeignField(modulus: bigint) { } } - function toFp(x: bigint | string | number | ForeignField) { + function toBigInt(x: bigint | string | number | ForeignField) { if (x instanceof ForeignField) return x.toBigInt(); - return mod(BigInt(x), p); + return BigInt(x); + } + function toFp(x: bigint | string | number | ForeignField) { + return mod(toBigInt(x), p); } function toLimbs(x: bigint | number | string | ForeignField): Field3 { if (x instanceof ForeignField) return x.value; diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index bffa7b6dad..59bfc32937 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -34,12 +34,23 @@ const ForeignField = { return sum([x, y], [-1n], f); }, sum, + negate, mul: multiply, inv: inverse, div: divide, assertAlmostFieldElements, + + assertLessThan(x: Field3, f: bigint) { + // constant case + if (Field3.isConstant(x)) { + assert(Field3.toBigint(x) < f, 'assertLessThan: got x >= f'); + return; + } + // provable case + negate(x, f); // proves that x < f + }, }; /** @@ -71,6 +82,30 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { return result; } +/** + * negate() deserves a special case because we can fix the overflow to -1 + * and know that a result in range is mapped to a result in range again. + * + * because the result is range-checked, this also proves that x is canonical: + * + * `f - x \in [0, 2^3l) => x < x + (f - x) = f` + */ +function negate(x: Field3, f: bigint) { + if (Field3.isConstant(x)) { + return sum([Field3.from(0n), x], [-1n], f); + } + // provable case + x = toVars(x); + let zero = toVars(Field3.from(0n)); + let { result, overflow } = singleAdd(zero, x, -1n, f); + Gates.zero(...result); + multiRangeCheck(result); + + // fix the overflow to -1 + overflow.assertEquals(-1n); + return result; +} + /** * core building block for non-native addition * diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index fc2b45ac90..c4bb072f06 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -504,6 +504,33 @@ const Gadgets = { assertAlmostFieldElements(xs: Field3[], f: bigint) { ForeignField.assertAlmostFieldElements(xs, f); }, + + /** + * Prove that x < f for any constant f < 2^264. + * + * 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.assertAlmostFieldElements} + * and also uses more constraints; it should not be needed in most use cases. + * + * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to + * {@link ForeignField.assertAlmostFieldElements} which adds that check itself. + * + * @throws if x is greater or equal to f. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); + * + * // range check limbs of x + * Gadgets.multiRangeCheck(x); + * + * // prove that x is fully reduced mod f + * Gadgets.ForeignField.assertLessThan(x, f); + * ``` + */ + assertLessThan(x: Field3, f: bigint) { + ForeignField.assertLessThan(x, f); + }, }, /** From 0c8cae9b1617a4ed706a2331c36a3ec6afc7ae3b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:04:14 +0100 Subject: [PATCH 0882/1215] fix negation based less than --- src/lib/foreign-field.ts | 15 ++++++++++++--- src/lib/gadgets/foreign-field.ts | 33 +++++++------------------------- src/lib/gadgets/gadgets.ts | 11 +++++++++++ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 8efa4fc0ca..cd2f7fbb7d 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -6,6 +6,8 @@ import { Bool } from './bool.js'; import { Tuple, TupleN } from './util/types.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; +import { assert } from './gadgets/common.js'; +import { l3 } from './gadgets/range-check.js'; // external API export { createForeignField, ForeignField }; @@ -157,7 +159,7 @@ function createForeignField(modulus: bigint) { * i.e. lies in the range [0, p), where p is the foreign field modulus. */ assertCanonicalFieldElement() { - Gadgets.ForeignField.assertLessThan(this.value, p); + this.assertLessThan(p); } // arithmetic with full constraints, for safe use @@ -279,14 +281,21 @@ function createForeignField(modulus: bigint) { /** * Assert that this field element is less than a constant c: `x < c`. + * + * The constant must satisfy `0 <= c < 2^264`, otherwise an error is thrown. + * * @example * ```ts * x.assertLessThan(10); * ``` */ - assertLessThan(y: bigint | number, message?: string) { + assertLessThan(c: bigint | number, message?: string) { + assert( + c >= 0 && c < 1n << l3, + `ForeignField.assertLessThan(): expected c <= c < 2^264, got ${c}` + ); try { - Gadgets.ForeignField.assertLessThan(this.value, toBigInt(y)); + Gadgets.ForeignField.assertLessThan(this.value, toBigInt(c)); } catch (err) { throw withMessage(err, message); } diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 59bfc32937..6332394423 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -33,8 +33,10 @@ const ForeignField = { sub(x: Field3, y: Field3, f: bigint) { return sum([x, y], [-1n], f); }, + negate(x: Field3, f: bigint) { + return sum([Field3.from(0n), x], [-1n], f); + }, sum, - negate, mul: multiply, inv: inverse, @@ -49,7 +51,10 @@ const ForeignField = { return; } // provable case - negate(x, f); // proves that x < f + // 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 or 1) + ForeignField.negate(x, f - 1n); }, }; @@ -82,30 +87,6 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { return result; } -/** - * negate() deserves a special case because we can fix the overflow to -1 - * and know that a result in range is mapped to a result in range again. - * - * because the result is range-checked, this also proves that x is canonical: - * - * `f - x \in [0, 2^3l) => x < x + (f - x) = f` - */ -function negate(x: Field3, f: bigint) { - if (Field3.isConstant(x)) { - return sum([Field3.from(0n), x], [-1n], f); - } - // provable case - x = toVars(x); - let zero = toVars(Field3.from(0n)); - let { result, overflow } = singleAdd(zero, x, -1n, f); - Gates.zero(...result); - multiRangeCheck(result); - - // fix the overflow to -1 - overflow.assertEquals(-1n); - return result; -} - /** * core building block for non-native addition * diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index c4bb072f06..c7d7db0567 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -377,6 +377,17 @@ const Gadgets = { return ForeignField.sub(x, y, f); }, + /** + * Foreign field negation: `-x mod f = f - x` + * + * See {@link ForeignField.add} for assumptions and usage examples. + * + * @throws fails if `x > f`, where `f - x < 0`. + */ + neg(x: Field3, f: bigint) { + return ForeignField.negate(x, f); + }, + /** * Foreign field sum: `xs[0] + signs[0] * xs[1] + ... + signs[n-1] * xs[n] mod f` * From 8cefdbbc423d8f86de0d11a4ca1ff1dbfc660012 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 12:06:17 +0100 Subject: [PATCH 0883/1215] add left shift(temp) --- CHANGELOG.md | 4 +- src/examples/simple_zkapp.ts | 161 +-------------------------- src/lib/gadgets/bitwise.ts | 20 +++- src/lib/gadgets/bitwise.unit-test.ts | 42 +++++-- src/lib/gadgets/gadgets.ts | 47 ++++++-- src/lib/int.ts | 8 +- 6 files changed, 101 insertions(+), 181 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9756c0ec95..dae2023577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes -- Rename `Gadgets.rotate` to `Gadgets.rotate64` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 +- Rename `Gadgets.rotate()` to `Gadgets.rotate64()` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 +- Rename `Gadgets.{leftShift(), rightShift()}` to `Gadgets.{leftShift64(), rightShift64()}` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 ### Added - `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 +- `Gadgets.leftShift32()` for left shift over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.rangeCheck32()` range check for 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.addMod32()` addition modulo 2^32 https://github.com/o1-labs/o1js/pull/1259 diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index 26f379770d..2919ef0f73 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -8,159 +8,10 @@ import { SmartContract, Mina, AccountUpdate, - Bool, - PublicKey, + Gadgets, + Provable, } from 'o1js'; -import { getProfiler } from './utils/profiler.js'; - -const doProofs = true; - -const beforeGenesis = UInt64.from(Date.now()); - -class SimpleZkapp extends SmartContract { - @state(Field) x = State(); - - events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - - @method init() { - super.init(); - this.x.set(initialState); - } - - @method update(y: Field): Field { - this.account.provedState.requireEquals(Bool(true)); - this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); - this.emitEvent('update', y); - let x = this.x.get(); - this.x.requireEquals(x); - let newX = x.add(y); - this.x.set(newX); - return newX; - } - - /** - * This method allows a certain privileged account to claim half of the zkapp balance, but only once - * @param caller the privileged account - */ - @method payout(caller: PrivateKey) { - this.account.provedState.requireEquals(Bool(true)); - - // check that caller is the privileged account - let callerAddress = caller.toPublicKey(); - callerAddress.assertEquals(privilegedAddress); - - // assert that the caller account is new - this way, payout can only happen once - let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.requireEquals(Bool(true)); - // pay out half of the zkapp balance to the caller - let balance = this.account.balance.get(); - this.account.balance.requireEquals(balance); - let halfBalance = balance.div(2); - this.send({ to: callerAccountUpdate, amount: halfBalance }); - - // emit some events - this.emitEvent('payoutReceiver', callerAddress); - this.emitEvent('payout', halfBalance); - } -} - -const SimpleProfiler = getProfiler('Simple zkApp'); -SimpleProfiler.start('Simple zkApp test flow'); -let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); -Mina.setActiveInstance(Local); - -// a test account that pays all the fees, and puts additional funds into the zkapp -let { privateKey: senderKey, publicKey: sender } = Local.testAccounts[0]; - -// the zkapp account -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -// a special account that is allowed to pull out half of the zkapp balance, once -let privilegedKey = PrivateKey.fromBase58( - 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' -); -let privilegedAddress = privilegedKey.toPublicKey(); - -let initialBalance = 10_000_000_000; -let initialState = Field(1); -let zkapp = new SimpleZkapp(zkappAddress); - -if (doProofs) { - console.log('compile'); - console.time('compile'); - await SimpleZkapp.compile(); - console.timeEnd('compile'); -} - -console.log('deploy'); -let tx = await Mina.transaction(sender, () => { - let senderUpdate = AccountUpdate.fundNewAccount(sender); - senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); -}); -await tx.prove(); -await tx.sign([senderKey]).send(); - -console.log('initial state: ' + zkapp.x.get()); -console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); - -let account = Mina.getAccount(zkappAddress); -console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); - -console.log('update'); -tx = await Mina.transaction(sender, () => { - zkapp.update(Field(3)); -}); -await tx.prove(); -await tx.sign([senderKey]).send(); - -// pay more into the zkapp -- this doesn't need a proof -console.log('receive'); -tx = await Mina.transaction(sender, () => { - let payerAccountUpdate = AccountUpdate.createSigned(sender); - payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); -}); -await tx.sign([senderKey]).send(); - -console.log('payout'); -tx = await Mina.transaction(sender, () => { - AccountUpdate.fundNewAccount(sender); - zkapp.payout(privilegedKey); -}); -await tx.prove(); -await tx.sign([senderKey]).send(); -sender; - -console.log('final state: ' + zkapp.x.get()); -console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); - -console.log('try to payout a second time..'); -tx = await Mina.transaction(sender, () => { - zkapp.payout(privilegedKey); -}); -try { - await tx.prove(); - await tx.sign([senderKey]).send(); -} catch (err: any) { - console.log('Transaction failed with error', err.message); -} - -console.log('try to payout to a different account..'); -try { - tx = await Mina.transaction(sender, () => { - zkapp.payout(Local.testAccounts[2].privateKey); - }); - await tx.prove(); - await tx.sign([senderKey]).send(); -} catch (err: any) { - console.log('Transaction failed with error', err.message); -} - -console.log( - `should still be the same final balance: ${zkapp.account.balance - .get() - .div(1e9)} MINA` -); - -SimpleProfiler.stop().store(); +let value = Field(852431261480n); +let bits = 31; +Fp.leftShift(x, 12, 32); +console.log(Gadgets.leftShift32(value, 12).toBigInt().toString(2).length); diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 382678a8eb..8c67104aac 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -13,7 +13,16 @@ import { import { rangeCheck32, rangeCheck64 } from './range-check.js'; import { divMod32 } from './arithmetic.js'; -export { xor, not, rotate64, rotate32, and, rightShift, leftShift }; +export { + xor, + not, + rotate64, + rotate32, + and, + rightShift64, + leftShift64, + leftShift32, +}; function not(a: Field, length: number, checked: boolean = false) { // check that input length is positive @@ -310,7 +319,7 @@ function rot64( return [rotated, excess, shifted]; } -function rightShift(field: Field, bits: number) { +function rightShift64(field: Field, bits: number) { assert( bits >= 0 && bits <= MAX_BITS, `rightShift: expected bits to be between 0 and 64, got ${bits}` @@ -327,7 +336,7 @@ function rightShift(field: Field, bits: number) { return excess; } -function leftShift(field: Field, bits: number) { +function leftShift64(field: Field, bits: number) { assert( bits >= 0 && bits <= MAX_BITS, `rightShift: expected bits to be between 0 and 64, got ${bits}` @@ -343,3 +352,8 @@ function leftShift(field: Field, bits: number) { const [, , shifted] = rot64(field, bits, 'left'); return shifted; } + +function leftShift32(field: Field, bits: number) { + let { remainder: shifted } = divMod32(field.mul(1n << BigInt(bits))); + return shifted; +} diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 3b6183bfdd..90f863629a 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -69,16 +69,22 @@ let Bitwise = ZkProgram({ return Gadgets.rotate64(a, 12, 'left'); }, }, - leftShift: { + leftShift64: { privateInputs: [Field], method(a: Field) { - return Gadgets.leftShift(a, 12); + return Gadgets.leftShift64(a, 12); }, }, - rightShift: { + leftShift32: { privateInputs: [Field], method(a: Field) { - return Gadgets.rightShift(a, 12); + return Gadgets.leftShift32(a, 12); + }, + }, + rightShift64: { + privateInputs: [Field], + method(a: Field) { + return Gadgets.rightShift64(a, 12); }, }, }, @@ -114,11 +120,11 @@ await Bitwise.compile(); ); equivalent({ from: [uint(length)], to: field })( (x) => Fp.leftShift(x, 12), - (x) => Gadgets.leftShift(x, 12) + (x) => Gadgets.leftShift64(x, 12) ); equivalent({ from: [uint(length)], to: field })( (x) => Fp.rightShift(x, 12), - (x) => Gadgets.rightShift(x, 12) + (x) => Gadgets.rightShift64(x, 12) ); }); @@ -127,6 +133,10 @@ await Bitwise.compile(); (x) => Fp.rot(x, 12n, 'left', 32n), (x) => Gadgets.rotate32(x, 12, 'left') ); + equivalent({ from: [uint(length)], to: field })( + (x) => Fp.leftShift(x, 12, 32), + (x) => Gadgets.leftShift32(x, 12) + ); }); await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs: 3 })( @@ -202,7 +212,19 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return Fp.leftShift(x, 12); }, async (x) => { - let proof = await Bitwise.leftShift(x); + let proof = await Bitwise.leftShift64(x); + return proof.publicOutput; + } +); + +await equivalentAsync({ from: [field], to: field }, { runs: 3 })( + (x) => { + console.log('input', x); + if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + return Fp.leftShift(x, 12, 32); + }, + async (x) => { + let proof = await Bitwise.leftShift32(x); return proof.publicOutput; } ); @@ -213,7 +235,7 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( return Fp.rightShift(x, 12); }, async (x) => { - let proof = await Bitwise.rightShift(x); + let proof = await Bitwise.rightShift64(x); return proof.publicOutput; } ); @@ -254,5 +276,5 @@ let isJustRotate = ifNotAllConstant( ); constraintSystem.fromZkProgram(Bitwise, 'rot64', isJustRotate); -constraintSystem.fromZkProgram(Bitwise, 'leftShift', isJustRotate); -constraintSystem.fromZkProgram(Bitwise, 'rightShift', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'leftShift64', isJustRotate); +constraintSystem.fromZkProgram(Bitwise, 'rightShift64', isJustRotate); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0a29968d87..36d5b956c9 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -15,8 +15,9 @@ import { rotate64, xor, and, - leftShift, - rightShift, + leftShift64, + rightShift64, + leftShift32, } from './bitwise.js'; import { Field } from '../core.js'; import { ForeignField, Field3 } from './foreign-field.js'; @@ -314,17 +315,47 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary - * const y = Gadgets.leftShift(x, 2); // left shift by 2 bits + * const y = Gadgets.leftShift64(x, 2); // left shift by 2 bits * y.assertEquals(0b110000); // 48 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ - leftShift(field: Field, bits: number) { - return leftShift(field, bits); + leftShift64(field: Field, bits: number) { + return leftShift64(field, bits); }, + /** + * Performs a left shift operation on the provided {@link Field} element. + * This operation is similar to the `<<` shift operation in JavaScript, + * where bits are shifted to the left, and the overflowing bits are discarded. + * + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * + * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * + * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * + * @param field {@link Field} element to shift. + * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 32 (or else the shift will fail). + * + * @throws Throws an error if the input value exceeds 32 bits. + * + * @example + * ```ts + * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary + * const y = Gadgets.leftShift32(x, 2); // left shift by 2 bits + * y.assertEquals(0b110000); // 48 in binary + * + * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); + * leftShift(xLarge, 32); // throws an error since input exceeds 32 bits + * ``` + */ + leftShift32(field: Field, bits: number) { + return leftShift32(field, bits); + }, /** * Performs a right shift operation on the provided {@link Field} element. * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. @@ -347,15 +378,15 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary - * const y = Gadgets.rightShift(x, 2); // right shift by 2 bits + * const y = Gadgets.rightShift64(x, 2); // right shift by 2 bits * y.assertEquals(0b000011); // 3 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ - rightShift(field: Field, bits: number) { - return rightShift(field, bits); + rightShift64(field: Field, bits: number) { + return rightShift64(field, bits); }, /** * Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND). diff --git a/src/lib/int.ts b/src/lib/int.ts index 830b5f34d4..824a282fc4 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -317,7 +317,7 @@ class UInt64 extends CircuitValue { * ``` */ leftShift(bits: number) { - return Gadgets.leftShift(this.value, bits); + return Gadgets.leftShift64(this.value, bits); } /** @@ -338,7 +338,7 @@ class UInt64 extends CircuitValue { * ``` */ rightShift(bits: number) { - return Gadgets.rightShift(this.value, bits); + return Gadgets.leftShift64(this.value, bits); } /** @@ -817,7 +817,7 @@ class UInt32 extends CircuitValue { * ``` */ leftShift(bits: number) { - return Gadgets.leftShift(this.value, bits); + return Gadgets.leftShift32(this.value, bits); } /** @@ -838,7 +838,7 @@ class UInt32 extends CircuitValue { * ``` */ rightShift(bits: number) { - return Gadgets.rightShift(this.value, bits); + return Gadgets.rightShift64(this.value, bits); } /** From 6da47bda4cf254247c19f656b5db87c7688194a1 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 29 Nov 2023 12:07:05 +0100 Subject: [PATCH 0884/1215] remove debug code --- src/examples/simple_zkapp.ts | 132 +++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 4 deletions(-) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index 2919ef0f73..ee4b76fd93 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -10,8 +10,132 @@ import { AccountUpdate, Gadgets, Provable, + Bool, } from 'o1js'; -let value = Field(852431261480n); -let bits = 31; -Fp.leftShift(x, 12, 32); -console.log(Gadgets.leftShift32(value, 12).toBigInt().toString(2).length); + +import { getProfiler } from './utils/profiler.js'; +const doProofs = true; +const beforeGenesis = UInt64.from(Date.now()); +class SimpleZkapp extends SmartContract { + @state(Field) x = State(); + events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; + @method init() { + super.init(); + this.x.set(initialState); + } + @method update(y: Field): Field { + this.account.provedState.requireEquals(Bool(true)); + this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); + this.emitEvent('update', y); + let x = this.x.get(); + this.x.requireEquals(x); + let newX = x.add(y); + this.x.set(newX); + return newX; + } + /** + * This method allows a certain privileged account to claim half of the zkapp balance, but only once + * @param caller the privileged account + */ + @method payout(caller: PrivateKey) { + this.account.provedState.requireEquals(Bool(true)); + // check that caller is the privileged account + let callerAddress = caller.toPublicKey(); + callerAddress.assertEquals(privilegedAddress); + // assert that the caller account is new - this way, payout can only happen once + let callerAccountUpdate = AccountUpdate.create(callerAddress); + callerAccountUpdate.account.isNew.requireEquals(Bool(true)); + // pay out half of the zkapp balance to the caller + let balance = this.account.balance.get(); + this.account.balance.requireEquals(balance); + let halfBalance = balance.div(2); + this.send({ to: callerAccountUpdate, amount: halfBalance }); + // emit some events + this.emitEvent('payoutReceiver', callerAddress); + this.emitEvent('payout', halfBalance); + } +} +const SimpleProfiler = getProfiler('Simple zkApp'); +SimpleProfiler.start('Simple zkApp test flow'); +let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); +Mina.setActiveInstance(Local); +// a test account that pays all the fees, and puts additional funds into the zkapp +let { privateKey: senderKey, publicKey: sender } = Local.testAccounts[0]; +// the zkapp account +let zkappKey = PrivateKey.random(); +let zkappAddress = zkappKey.toPublicKey(); +// a special account that is allowed to pull out half of the zkapp balance, once +let privilegedKey = PrivateKey.fromBase58( + 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' +); +let privilegedAddress = privilegedKey.toPublicKey(); +let initialBalance = 10_000_000_000; +let initialState = Field(1); +let zkapp = new SimpleZkapp(zkappAddress); +if (doProofs) { + console.log('compile'); + console.time('compile'); + await SimpleZkapp.compile(); + console.timeEnd('compile'); +} +console.log('deploy'); +let tx = await Mina.transaction(sender, () => { + let senderUpdate = AccountUpdate.fundNewAccount(sender); + senderUpdate.send({ to: zkappAddress, amount: initialBalance }); + zkapp.deploy({ zkappKey }); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); +console.log('initial state: ' + zkapp.x.get()); +console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); +let account = Mina.getAccount(zkappAddress); +console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); +console.log('update'); +tx = await Mina.transaction(sender, () => { + zkapp.update(Field(3)); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); +// pay more into the zkapp -- this doesn't need a proof +console.log('receive'); +tx = await Mina.transaction(sender, () => { + let payerAccountUpdate = AccountUpdate.createSigned(sender); + payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); +}); +await tx.sign([senderKey]).send(); +console.log('payout'); +tx = await Mina.transaction(sender, () => { + AccountUpdate.fundNewAccount(sender); + zkapp.payout(privilegedKey); +}); +await tx.prove(); +await tx.sign([senderKey]).send(); +sender; +console.log('final state: ' + zkapp.x.get()); +console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); +console.log('try to payout a second time..'); +tx = await Mina.transaction(sender, () => { + zkapp.payout(privilegedKey); +}); +try { + await tx.prove(); + await tx.sign([senderKey]).send(); +} catch (err: any) { + console.log('Transaction failed with error', err.message); +} +console.log('try to payout to a different account..'); +try { + tx = await Mina.transaction(sender, () => { + zkapp.payout(Local.testAccounts[2].privateKey); + }); + await tx.prove(); + await tx.sign([senderKey]).send(); +} catch (err: any) { + console.log('Transaction failed with error', err.message); +} +console.log( + `should still be the same final balance: ${zkapp.account.balance + .get() + .div(1e9)} MINA` +); +SimpleProfiler.stop().store(); From e449484fee62ba7fc386f5c41c72d250877d1970 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:11:47 +0100 Subject: [PATCH 0885/1215] update top level doccomment --- src/lib/foreign-field.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index cd2f7fbb7d..0e7024bf7d 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -32,26 +32,17 @@ type ForeignField = InstanceType>; * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), * as well as helper methods like `assertEquals()` and `equals()`. * - * _Advanced usage:_ + * _Advanced details:_ * * Internally, a foreign field element is represented as three native field elements, each of which * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. - * With default parameters, new `ForeignField` elements introduced in provable code are automatically - * constrained to be valid on creation. + * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, + * see {@link ForeignField.assertAlmostFieldElement} for more details. + * If you need to prove that you have a fully reduced field element, use {@link ForeignField.assertCanonicalFieldElement}: * - * However, optimized code may want to forgo these automatic checks because in some - * situations they are redundant. Skipping automatic validity checks can be done - * by passing the `unsafe: true` flag: - * - * ```ts - * class UnsafeField extends createForeignField(17n, { unsafe: true }) {} - * ``` - * - * You then often need to manually add validity checks: * ```ts - * let x: UnsafeField; - * x.assertValidElement(); // prove that x is a valid foreign field element + * x.assertCanonicalFieldElement(); // x < p * ``` * * @param modulus the modulus of the finite field you are instantiating From 6d5d5b80c6f0ab3866ac5e8b80727639b8722468 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:12:47 +0100 Subject: [PATCH 0886/1215] resuse constant --- src/lib/foreign-field.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 0e7024bf7d..3e9d59c1b2 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -7,16 +7,11 @@ import { Tuple, TupleN } from './util/types.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './gadgets/common.js'; -import { l3 } from './gadgets/range-check.js'; +import { l3, l } from './gadgets/range-check.js'; // external API export { createForeignField, ForeignField }; -// internal API -export { limbBits }; - -const limbBits = 88n; - type ForeignField = InstanceType>; /** @@ -316,7 +311,7 @@ function createForeignField(modulus: bigint) { toBits(length = sizeInBits) { checkBitLength('ForeignField.toBits()', length, sizeInBits); let [l0, l1, l2] = this.value; - let limbSize = Number(limbBits); + let limbSize = Number(l); let xBits = l0.toBits(Math.min(length, limbSize)); length -= limbSize; if (length <= 0) return xBits; @@ -335,7 +330,7 @@ function createForeignField(modulus: bigint) { static fromBits(bits: Bool[]) { let length = bits.length; checkBitLength('ForeignField.fromBits()', length, sizeInBits); - let limbSize = Number(limbBits); + 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)); @@ -401,5 +396,5 @@ function createForeignField(modulus: bigint) { // see RFC: https://github.com/o1-labs/proof-systems/blob/1fdb1fd1d112f9d4ee095dbb31f008deeb8150b0/book/src/rfcs/foreign_field_mul.md // since p = 2^254 + eps for both Pasta fields with eps small, a fairly tight lower bound is // f_max >= sqrt(2^254 * 2^264) = 2^259 -const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * limbBits) / 2n; +const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * l) / 2n; const foreignFieldMax = 1n << foreignFieldMaxBits; From d984e01587c6b6b652d41c72002150650b28bcf1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:20:02 +0100 Subject: [PATCH 0887/1215] more precise weak bound --- src/lib/gadgets/foreign-field.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 6332394423..65961c2aa6 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -341,6 +341,12 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { } function weakBound(x: Field, f: bigint) { + // if f0, f1 === 0, we can use a stronger bound x[2] < f2 + // because this is true for all field elements x in [0,f) + if ((f & l2Mask) === 0n) { + return x.add(lMask + 1n - (f >> l2)); + } + // otherwise, we use x[2] < f2 + 1, so we allow x[2] === f2 return x.add(lMask - (f >> l2)); } From fbfe81531727c6ea291650586aa7367b9cea0026 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:44:43 +0100 Subject: [PATCH 0888/1215] adapt unit test --- src/lib/foreign-field.unit-test.ts | 203 +++++++---------------------- 1 file changed, 47 insertions(+), 156 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index f77d281cdf..9534e87cee 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,15 +1,22 @@ import { ProvablePure } from '../snarky.js'; -import { Group } from './core.js'; -import { Field, FieldVar } from './field.js'; -import { ForeignField, createForeignField, limbBits } from './foreign-field.js'; +import { Field, Group } from './core.js'; +import { ForeignField, createForeignField } from './foreign-field.js'; import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; -import { createEquivalenceTesters, throwError } from './testing/equivalent.js'; +import { + ProvableSpec, + bool, + equivalentProvable as equivalent, + throwError, + unit, +} from './testing/equivalent.js'; import { test, Random } from './testing/property.js'; import { Provable } from './provable.js'; import { ZkProgram } from './proof_system.js'; import { Circuit, circuitMain } from './circuit.js'; import { Scalar } from './scalar.js'; +import { l } from './gadgets/range-check.js'; +import { assert } from './gadgets/common.js'; // toy example - F_17 @@ -29,14 +36,14 @@ expect(() => createForeignField(1n << 260n)).toThrow( class ForeignScalar extends createForeignField(Fq.modulus) {} // types -ForeignScalar satisfies ProvablePure; +ForeignScalar.provable satisfies ProvablePure; // basic constructor / IO { - let s0 = 1n + ((1n + (1n << limbBits)) << limbBits); + let s0 = 1n + ((1n + (1n << l)) << l); let scalar = new ForeignScalar(s0); - expect(scalar.value).toEqual([0, FieldVar[1], FieldVar[1], FieldVar[1]]); + expect(scalar.value).toEqual([Field(1), Field(1), Field(1)]); expect(scalar.toBigInt()).toEqual(s0); } @@ -48,123 +55,44 @@ test(Random.scalar, (x0, assert) => { // test equivalence of in-SNARK and out-of-SNARK operations -let { equivalent1, equivalent2, equivalentBool2, equivalentVoid2 } = - createEquivalenceTesters(ForeignScalar, (x) => new ForeignScalar(x)); +let f: ProvableSpec = { + rng: Random.scalar, + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.provable, +}; // arithmetic -equivalent2((x, y) => x.add(y), Fq.add, Random.scalar); -equivalent1((x) => x.neg(), Fq.negate, Random.scalar); -equivalent2((x, y) => x.sub(y), Fq.sub, Random.scalar); -equivalent2((x, y) => x.mul(y), Fq.mul, Random.scalar); -equivalent1( - (x) => x.inv(), +equivalent({ from: [f, f], to: f })(Fq.add, (x, y) => x.add(y)); +equivalent({ from: [f, f], to: f })(Fq.sub, (x, y) => x.sub(y)); +equivalent({ from: [f], to: f })(Fq.negate, (x) => x.neg()); +equivalent({ from: [f, f], to: f })(Fq.mul, (x, y) => x.mul(y)); +equivalent({ from: [f], to: f })( (x) => Fq.inverse(x) ?? throwError('division by 0'), - Random.scalar + (x) => x.inv() ); +// equivalent({ from: [f, f], to: f })( +// (x, y) => Fq.div(x, y) ?? throwError('division by 0'), +// (x, y) => x.div(y) +// ); // equality -equivalentBool2( - (x, y) => x.equals(y), +equivalent({ from: [f, f], to: bool })( (x, y) => x === y, - Random.scalar + (x, y) => x.equals(y) ); -equivalentVoid2( - (x, y) => x.assertEquals(y), +equivalent({ from: [f, f], to: unit })( (x, y) => x === y || throwError('not equal'), - Random.scalar + (x, y) => x.assertEquals(y) ); // toBits / fromBits -equivalent1( +equivalent({ from: [f], to: f })( + (x) => x, (x) => { let bits = x.toBits(); expect(bits.length).toEqual(255); return ForeignScalar.fromBits(bits); - }, - (x) => x, - Random.scalar -); - -// test random sum chains up to length 20 - -test( - Random.array( - Random.record({ - scalar: Random.scalar, - operation: Random.oneOf<[1, -1]>(1, -1), - }), - Random.nat(20) - ), - (sumSpec) => { - if (sumSpec.length === 0) return; - - let scalars = sumSpec.map((s) => s.scalar); - let operations = sumSpec.slice(1).map((s) => s.operation); - let functions = operations.map((op) => (op === 1 ? Fq.add : Fq.sub)); - - // compute sum on bigints - let sum = scalars.reduce( - (sum, s, i) => (i === 0 ? s : functions[i - 1](sum, s)), - 0n - ); - - // check that the expected sum is computed in provable code - - function main() { - let scalarVars = scalars.map((s) => - Provable.witness(ForeignScalar, () => new ForeignScalar(s)) - ); - let z = ForeignScalar.sum(scalarVars, operations); - Provable.asProver(() => expect(z.toBigInt()).toEqual(sum)); - } - - Provable.runAndCheck(main); - - // check that the expected gates are created - - let expectedGateTypes: GateType[] = []; - - let boundsCheck: GateType[] = [ - 'ForeignFieldAdd', - 'Zero', - 'RangeCheck0', - 'RangeCheck0', - 'RangeCheck1', - 'Zero', - ]; - - // for every witnessed scalar, add gates for the bounds check - scalars.forEach(() => expectedGateTypes.push(...boundsCheck)); - - // now, add as many ForeignFieldAdd gates as there are additions - operations.forEach(() => expectedGateTypes.push('ForeignFieldAdd')); - - // add a final bound check for the result - expectedGateTypes.push(...boundsCheck); - - // compute the actual gates - let { gates } = Provable.constraintSystem(main); - - // split out all generic gates - let generics = gates.filter((g) => g.type === 'Generic'); - gates = gates.filter((g) => g.type !== 'Generic'); - let gateTypes = gates.map((g) => g.type); - - // check that gates without generics are as expected - // TODO: reenable after adapting to new gadget layout! - // expect(gateTypes).toEqual(expectedGateTypes); - - // check that generic gates correspond to adding one of the constants 0, 1 and 2^88 (the limb size) - let allowedConstants = new Set([0n, 1n, 1n << 88n]); - let ok = generics.every(({ coeffs: [left, right, out, mul, constant] }) => { - let isConstantGate = - ((left === '0' && right === '1') || (left === '1' && right === '0')) && - out === '0' && - mul === '0'; - let constantValue = Field.ORDER - BigInt(constant); - return isConstantGate && allowedConstants.has(constantValue); - }); - expect(ok).toBe(true); } ); @@ -189,7 +117,7 @@ let pointBigint = G.scale(G.generatorMina, scalarBigint); // then convert to scalar from bits (which shifts it back) and scale a point by the scalar function main0() { let ffScalar = Provable.witness( - ForeignScalar, + ForeignScalar.provable, () => new ForeignScalar(scalarBigint) ); let bitsUnshifted = unshift(ffScalar).toBits(); @@ -204,7 +132,7 @@ function main0() { // = same end result as main0 function main1() { let ffScalar = Provable.witness( - ForeignScalar, + ForeignScalar.provable, () => new ForeignScalar(scalarBigint) ); let bits = ffScalar.toBits(); @@ -226,61 +154,24 @@ let { rows: rows0 } = Provable.constraintSystem(main0); let { rows: rows1 } = Provable.constraintSystem(main1); expect(rows0 + 100).toBeLessThan(rows1); -// tests with proving - -function simpleMain() { - let s = Provable.witness( - ForeignScalar, - () => new ForeignScalar(scalarBigint) - ); - s.mul(oneHalf); -} +// test with proving class Main extends Circuit { @circuitMain static main() { - simpleMain(); + main0(); } } -console.log('compiling'); let kp = await Main.generateKeypair(); let cs = kp.constraintSystem(); -// console.log(JSON.stringify(cs.filter((g) => g.type !== 'Zero'))); -console.log('# rows', cs.length); - -console.log('proving'); -let proof0 = await Main.prove([], [], kp); - -console.log('verifying'); -let ok = await Main.verify([], kp.verificationKey(), proof0); -console.log('verifies?', ok); - -let Program = ZkProgram({ - methods: { - test: { - privateInputs: [], - method() { - simpleMain(); - }, - }, - }, -}); - -console.log('compiling'); -await Program.compile(); - -console.log('proving'); -let proof = await Program.test(); +assert( + cs.length === 1 << 13, + `should have ${cs.length} = 2^13 rows, the smallest supported number` +); -console.log('verifying'); -ok = await Program.verify(proof); -console.log('verifies?', ok); +let proof = await Main.prove([], [], kp); -type GateType = - | 'Zero' - | 'Generic' - | 'RangeCheck0' - | 'RangeCheck1' - | 'ForeignFieldAdd'; +let ok = await Main.verify([], kp.verificationKey(), proof); +assert(ok, 'proof should verify'); From 2f18e5fdee6fd73b55c824e5cd9aaa366304a6ce Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 12:47:51 +0100 Subject: [PATCH 0889/1215] fix tests --- src/examples/simple_zkapp.ts | 3 +-- tests/vk-regression/plain-constraint-system.ts | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index ee4b76fd93..5ea4f94570 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -8,9 +8,8 @@ import { SmartContract, Mina, AccountUpdate, - Gadgets, - Provable, Bool, + PublicKey, } from 'o1js'; import { getProfiler } from './utils/profiler.js'; diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index 6a38b928fb..fc7d8e1797 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -73,13 +73,13 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { }, leftShift() { let a = Provable.witness(Field, () => new Field(12)); - Gadgets.leftShift(a, 2); - Gadgets.leftShift(a, 4); + Gadgets.leftShift64(a, 2); + Gadgets.leftShift64(a, 4); }, rightShift() { let a = Provable.witness(Field, () => new Field(12)); - Gadgets.rightShift(a, 2); - Gadgets.rightShift(a, 4); + Gadgets.rightShift64(a, 2); + Gadgets.rightShift64(a, 4); }, and() { let a = Provable.witness(Field, () => new Field(5n)); From acd3f0510ced8a0db2f068a6fe5adba152649402 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 12:55:14 +0100 Subject: [PATCH 0890/1215] add division and necessary bounds checks --- src/lib/foreign-field.ts | 17 +++++++++++++++++ src/lib/foreign-field.unit-test.ts | 9 ++++----- src/lib/gadgets/foreign-field.ts | 4 ++-- src/lib/gadgets/gadgets.ts | 8 ++++++-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 3e9d59c1b2..beb3a58e20 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -208,6 +208,8 @@ function createForeignField(modulus: bigint) { let fields = xs.map(toLimbs); let ops = operations.map((op) => (op === 1 ? 1n : -1n)); let z = Gadgets.ForeignField.sum(fields, ops, p); + // TODO inefficient: add bound check on the result + Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); return new ForeignField(z); } @@ -220,6 +222,8 @@ function createForeignField(modulus: bigint) { */ mul(y: ForeignField | bigint | number) { let z = Gadgets.ForeignField.mul(this.value, toLimbs(y), p); + // TODO inefficient: add bound check on the result + Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); return new ForeignField(z); } @@ -236,6 +240,19 @@ function createForeignField(modulus: bigint) { return new ForeignField(z); } + /** + * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. + * @example + * ```ts + * let z = x.div(y); // x/y mod p + * z.mul(y).assertEquals(x); + * ``` + */ + div(y: ForeignField | bigint | number) { + let z = Gadgets.ForeignField.div(this.value, toLimbs(y), p); + return new ForeignField(z); + } + // convenience methods /** diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 9534e87cee..3fcedf8969 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -12,7 +12,6 @@ import { } from './testing/equivalent.js'; import { test, Random } from './testing/property.js'; import { Provable } from './provable.js'; -import { ZkProgram } from './proof_system.js'; import { Circuit, circuitMain } from './circuit.js'; import { Scalar } from './scalar.js'; import { l } from './gadgets/range-check.js'; @@ -71,10 +70,10 @@ equivalent({ from: [f], to: f })( (x) => Fq.inverse(x) ?? throwError('division by 0'), (x) => x.inv() ); -// equivalent({ from: [f, f], to: f })( -// (x, y) => Fq.div(x, y) ?? throwError('division by 0'), -// (x, y) => x.div(y) -// ); +equivalent({ from: [f, f], to: f })( + (x, y) => Fq.div(x, y) ?? throwError('division by 0'), + (x, y) => x.div(y) +); // equality equivalent({ from: [f, f], to: bool })( diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 65961c2aa6..1290d1f264 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -354,11 +354,11 @@ function weakBound(x: Field, f: bigint) { * Apply range checks and weak bounds checks to a list of Field3s. * Optimal if the list length is a multiple of 3. */ -function assertAlmostFieldElements(xs: Field3[], f: bigint) { +function assertAlmostFieldElements(xs: Field3[], f: bigint, skipMrc = false) { let bounds: Field[] = []; for (let x of xs) { - multiRangeCheck(x); + if (!skipMrc) multiRangeCheck(x); bounds.push(weakBound(x[2], f)); if (TupleN.hasLength(3, bounds)) { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index c7d7db0567..7f16f4ae9f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -512,8 +512,12 @@ const Gadgets = { * ForeignField.assertAlmostFieldElements([xy], f); // TODO: would be more efficient to batch this with 2 other elements * ``` */ - assertAlmostFieldElements(xs: Field3[], f: bigint) { - ForeignField.assertAlmostFieldElements(xs, f); + assertAlmostFieldElements( + xs: Field3[], + f: bigint, + { skipMrc = false } = {} + ) { + ForeignField.assertAlmostFieldElements(xs, f, skipMrc); }, /** From 1b3ce9b6f709390218dc38ba96d5fa74fb107140 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 13:24:46 +0100 Subject: [PATCH 0891/1215] handle constant case --- src/lib/gadgets/range-check.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 273b86b4a7..c090c483ef 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -4,6 +4,7 @@ import { Field as FieldProvable } from '../../provable/field-bigint.js'; import { Field } from '../field.js'; import { Gates } from '../gates.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; +import { Bool } from '../bool.js'; export { rangeCheck64, @@ -259,7 +260,23 @@ function rangeCheckHelper(length: number, x: Field) { /** * Asserts that x is in the range [0, 2^n) */ -function rangeCheckN(n: number, x: Field, message?: string) { +function rangeCheckN(n: number, x: Field, message: string = '') { + assert( + length <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + assert(length > 0, `bit length must be positive, got ${length}`); + assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + + if (x.isConstant()) { + if (x.toBigInt() >= 1n << BigInt(n)) { + throw Error( + `rangeCheckN: expected field to fit in ${n} bits, got ${x}.\n${message}` + ); + } + return; + } + let actual = rangeCheckHelper(n, x); actual.assertEquals(x, message); } @@ -268,6 +285,17 @@ function rangeCheckN(n: number, x: Field, message?: string) { * Checks that x is in the range [0, 2^n) and returns a Boolean indicating whether the check passed. */ function isInRangeN(n: number, x: Field) { + assert( + length <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + assert(length > 0, `bit length must be positive, got ${length}`); + assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + + if (x.isConstant()) { + return new Bool(x.toBigInt() < 1n << BigInt(n)); + } + let actual = rangeCheckHelper(n, x); return actual.equals(x); } From 803665e9bb1af3ba1cab1ab54c514b40118c960a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 14:38:25 +0100 Subject: [PATCH 0892/1215] move foreign field class outside function so that the type is not lost --- src/lib/foreign-field.ts | 729 ++++++++++++++++++++------------------- 1 file changed, 383 insertions(+), 346 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index beb3a58e20..a50a504aac 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -10,357 +10,412 @@ import { assert } from './gadgets/common.js'; import { l3, l } from './gadgets/range-check.js'; // external API -export { createForeignField, ForeignField }; +export { createForeignField }; +export type { ForeignField }; -type ForeignField = InstanceType>; +class ForeignField { + static _modulus: bigint | undefined = undefined; -/** - * Create a class representing a prime order finite field, which is different from the native {@link Field}. - * - * ```ts - * const SmallField = createForeignField(17n); // the finite field F_17 - * ``` - * - * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. - * We support prime moduli up to a size of 259 bits. - * - * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), - * as well as helper methods like `assertEquals()` and `equals()`. - * - * _Advanced details:_ - * - * Internally, a foreign field element is represented as three native field elements, each of which - * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs - * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. - * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, - * see {@link ForeignField.assertAlmostFieldElement} for more details. - * If you need to prove that you have a fully reduced field element, use {@link ForeignField.assertCanonicalFieldElement}: - * - * ```ts - * x.assertCanonicalFieldElement(); // x < p - * ``` - * - * @param modulus the modulus of the finite field you are instantiating - */ -function createForeignField(modulus: bigint) { - const p = modulus; + // static parameters + static get modulus() { + assert(this._modulus !== undefined, 'ForeignField class not initialized.'); + return this._modulus; + } + get modulus() { + return (this.constructor as typeof ForeignField).modulus; + } + static get sizeInBits() { + return this.modulus.toString(2).length; + } - if (p <= 0) { - throw Error(`ForeignField: expected modulus to be positive, got ${p}`); + /** + * The internal representation of a foreign field element, as a tuple of 3 limbs. + */ + value: Field3; + + /** + * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. + * @example + * ```ts + * let x = new ForeignField(5); + * ``` + */ + constructor(x: ForeignField | Field3 | bigint | number | string) { + const p = this.modulus; + if (x instanceof ForeignField) { + this.value = x.value; + return; + } + // Field3 + if (Array.isArray(x)) { + this.value = x; + return; + } + // constant + this.value = Field3.from(mod(BigInt(x), p)); } - if (p > foreignFieldMax) { - throw Error( - `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` - ); + + private static toLimbs(x: bigint | number | string | ForeignField): Field3 { + if (x instanceof ForeignField) return x.value; + return Field3.from(mod(BigInt(x), this.modulus)); + } + private get class() { + return this.constructor as typeof ForeignField; } - let sizeInBits = p.toString(2).length; + /** + * Coerce the input to a {@link ForeignField}. + */ + static from(x: ForeignField | Field3 | bigint | number | string) { + if (x instanceof ForeignField) return x; + return new this(x); + } - class ForeignField { - static modulus = p; - value: Field3; + /** + * Checks whether this field element is a constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Field3.isConstant(this.value); + } - static #zero = new ForeignField(0); + /** + * Convert this field element to a constant. + * + * See {@link FieldVar} to understand constants vs variables. + * + * **Warning**: This function is only useful in {@link Provable.witness} or {@link Provable.asProver} blocks, + * that is, in situations where the prover computes a value outside provable code. + */ + toConstant(): ForeignField { + let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); + return new this.class(constantLimbs); + } - /** - * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. - * @example - * ```ts - * let x = new ForeignField(5); - * ``` - */ - constructor(x: ForeignField | Field3 | bigint | number | string) { - if (x instanceof ForeignField) { - this.value = x.value; - return; - } - // Field3 - if (Array.isArray(x)) { - this.value = x; - return; - } - // constant - this.value = Field3.from(mod(BigInt(x), p)); - } + /** + * Convert this field element to a bigint. + */ + toBigInt() { + return Field3.toBigint(this.value); + } - /** - * Coerce the input to a {@link ForeignField}. - */ - static from(x: ForeignField | Field3 | bigint | number | string) { - if (x instanceof ForeignField) return x; - return new ForeignField(x); - } + /** + * Assert that this field element lies in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * **Warning**: This check is added to all `ForeignField` elements by default. + * You don't have to use it. + * + * Note: this does not ensure that the field elements is in the canonical range [0, p). + * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. + * + * We use the weaker property by default because it is cheaper to prove and sufficient for + * ensuring validity of all our non-native field arithmetic methods. + */ + static assertAlmostFieldElement(x: ForeignField) { + // TODO: this is not very efficient, but the only way to abstract away the complicated + // range check assumptions and also not introduce a global context of pending range checks. + // we plan to get rid of bounds checks anyway, then this is just a multi-range check + Gadgets.ForeignField.assertAlmostFieldElements([x.value], this.modulus); + } - /** - * Checks whether this field element is a constant. - * - * See {@link FieldVar} to understand constants vs variables. - */ - isConstant() { - return Field3.isConstant(this.value); - } + /** + * Assert that this field element is fully reduced, + * i.e. lies in the range [0, p), where p is the foreign field modulus. + */ + assertCanonicalFieldElement() { + const p = this.modulus; + this.assertLessThan(p); + } - /** - * Convert this field element to a constant. - * - * See {@link FieldVar} to understand constants vs variables. - * - * **Warning**: This function is only useful in {@link Provable.witness} or {@link Provable.asProver} blocks, - * that is, in situations where the prover computes a value outside provable code. - */ - toConstant(): ForeignField { - let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); - return new ForeignField(constantLimbs); - } + // arithmetic with full constraints, for safe use + + /** + * Finite field addition + * @example + * ```ts + * x.add(2); // x + 2 mod p + * ``` + */ + add(y: ForeignField | bigint | number) { + return this.class.sum([this, y], [1]); + } - /** - * Convert this field element to a bigint. - */ - toBigInt() { - return Field3.toBigint(this.value); - } + /** + * Finite field negation + * @example + * ```ts + * x.neg(); // -x mod p = p - x + * ``` + */ + neg() { + return this.class.sum([this.class.from(0n), this], [-1]); + } - /** - * Assert that this field element lies in the range [0, 2^k), - * where k = ceil(log2(p)) and p is the foreign field modulus. - * - * **Warning**: This check is added to all `ForeignField` elements by default. - * You don't have to use it. - * - * Note: this does not ensure that the field elements is in the canonical range [0, p). - * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. - * - * We use the weaker property by default because it is cheaper to prove and sufficient for - * ensuring validity of all our non-native field arithmetic methods. - */ - assertAlmostFieldElement() { - // TODO: this is not very efficient, but the only way to abstract away the complicated - // range check assumptions and also not introduce a global context of pending range checks. - // we plan to get rid of bounds checks anyway, then this is just a multi-range check - Gadgets.ForeignField.assertAlmostFieldElements([this.value], p); - } + /** + * Finite field subtraction + * @example + * ```ts + * x.sub(1); // x - 1 mod p + * ``` + */ + sub(y: ForeignField | bigint | number) { + return this.class.sum([this, y], [-1]); + } - /** - * Assert that this field element is fully reduced, - * i.e. lies in the range [0, p), where p is the foreign field modulus. - */ - assertCanonicalFieldElement() { - this.assertLessThan(p); - } + /** + * Sum (or difference) of multiple finite field elements. + * + * @example + * ```ts + * let z = ForeignField.sum([3, 2, 1], [-1, 1]); // 3 - 2 + 1 + * z.assertEquals(2); + * ``` + * + * This method expects a list of ForeignField-like values, `x0,...,xn`, + * and a list of "operations" `op1,...,opn` where every op is 1 or -1 (plus or minus), + * and returns + * + * `x0 + op1*x1 + ... + opn*xn` + * + * where the sum is computed in finite field arithmetic. + * + * **Important:** For more than two summands, this is significantly more efficient + * than chaining calls to {@link ForeignField.add} and {@link ForeignField.sub}. + * + */ + static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { + const p = this.modulus; + let fields = xs.map((x) => this.toLimbs(x)); + let ops = operations.map((op) => (op === 1 ? 1n : -1n)); + let z = Gadgets.ForeignField.sum(fields, ops, p); + // TODO inefficient: add bound check on the result + Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); + return new this(z); + } - // arithmetic with full constraints, for safe use - - /** - * Finite field addition - * @example - * ```ts - * x.add(2); // x + 2 mod p - * ``` - */ - add(y: ForeignField | bigint | number) { - return ForeignField.sum([this, y], [1]); - } + /** + * Finite field multiplication + * @example + * ```ts + * x.mul(y); // x*y mod p + * ``` + */ + mul(y: ForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.mul(this.value, this.class.toLimbs(y), p); + // TODO inefficient: add bound check on the result + Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); + return new this.class(z); + } - /** - * Finite field negation - * @example - * ```ts - * x.neg(); // -x mod p = p - x - * ``` - */ - neg() { - return ForeignField.sum([ForeignField.#zero, this], [-1]); - } + /** + * Multiplicative inverse in the finite field + * @example + * ```ts + * let z = x.inv(); // 1/x mod p + * z.mul(x).assertEquals(1); + * ``` + */ + inv(): ForeignField { + const p = this.modulus; + let z = Gadgets.ForeignField.inv(this.value, p); + return new this.class(z); + } - /** - * Finite field subtraction - * @example - * ```ts - * x.sub(1); // x - 1 mod p - * ``` - */ - sub(y: ForeignField | bigint | number) { - return ForeignField.sum([this, y], [-1]); - } + /** + * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. + * @example + * ```ts + * let z = x.div(y); // x/y mod p + * z.mul(y).assertEquals(x); + * ``` + */ + div(y: ForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.div(this.value, this.class.toLimbs(y), p); + return new this.class(z); + } - /** - * Sum (or difference) of multiple finite field elements. - * - * @example - * ```ts - * let z = ForeignField.sum([3, 2, 1], [-1, 1]); // 3 - 2 + 1 - * z.assertEquals(2); - * ``` - * - * This method expects a list of ForeignField-like values, `x0,...,xn`, - * and a list of "operations" `op1,...,opn` where every op is 1 or -1 (plus or minus), - * and returns - * - * `x0 + op1*x1 + ... + opn*xn` - * - * where the sum is computed in finite field arithmetic. - * - * **Important:** For more than two summands, this is significantly more efficient - * than chaining calls to {@link ForeignField.add} and {@link ForeignField.sub}. - * - */ - static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { - let fields = xs.map(toLimbs); - let ops = operations.map((op) => (op === 1 ? 1n : -1n)); - let z = Gadgets.ForeignField.sum(fields, ops, p); - // TODO inefficient: add bound check on the result - Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); - return new ForeignField(z); + // convenience methods + + /** + * Assert equality with a ForeignField-like value + * @example + * ```ts + * x.assertEquals(0, "x is zero"); + * ``` + */ + assertEquals(y: ForeignField | bigint | number, message?: string) { + const p = this.modulus; + try { + if (this.isConstant() && isConstant(y)) { + let x = this.toBigInt(); + let y0 = mod(toBigInt(y), p); + if (x !== y0) { + throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); + } + return; + } + return Provable.assertEqual( + this.class.provable, + this, + this.class.from(y) + ); + } catch (err) { + throw withMessage(err, message); } + } - /** - * Finite field multiplication - * @example - * ```ts - * x.mul(y); // x*y mod p - * ``` - */ - mul(y: ForeignField | bigint | number) { - let z = Gadgets.ForeignField.mul(this.value, toLimbs(y), p); - // TODO inefficient: add bound check on the result - Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); - return new ForeignField(z); + /** + * Assert that this field element is less than a constant c: `x < c`. + * + * The constant must satisfy `0 <= c < 2^264`, otherwise an error is thrown. + * + * @example + * ```ts + * x.assertLessThan(10); + * ``` + */ + assertLessThan(c: bigint | number, message?: string) { + assert( + c >= 0 && c < 1n << l3, + `ForeignField.assertLessThan(): expected c <= c < 2^264, got ${c}` + ); + try { + Gadgets.ForeignField.assertLessThan(this.value, toBigInt(c)); + } catch (err) { + throw withMessage(err, message); } + } - /** - * Multiplicative inverse in the finite field - * @example - * ```ts - * let z = x.inv(); // 1/x mod p - * z.mul(x).assertEquals(1); - * ``` - */ - inv(): ForeignField { - let z = Gadgets.ForeignField.inv(this.value, p); - return new ForeignField(z); + /** + * Check equality with a ForeignField-like value + * @example + * ```ts + * let isXZero = x.equals(0); + * ``` + */ + equals(y: ForeignField | bigint | number) { + const p = this.modulus; + if (this.isConstant() && isConstant(y)) { + return new Bool(this.toBigInt() === mod(toBigInt(y), p)); } + return Provable.equal(this.class.provable, this, this.class.from(y)); + } - /** - * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. - * @example - * ```ts - * let z = x.div(y); // x/y mod p - * z.mul(y).assertEquals(x); - * ``` - */ - div(y: ForeignField | bigint | number) { - let z = Gadgets.ForeignField.div(this.value, toLimbs(y), p); - return new ForeignField(z); - } + // bit packing + + /** + * Unpack a field element to its bits, as a {@link Bool}[] array. + * + * This method is provable! + */ + toBits(length?: number) { + const sizeInBits = this.class.sizeInBits; + if (length === undefined) length = sizeInBits; + checkBitLength('ForeignField.toBits()', length, sizeInBits); + let [l0, l1, l2] = this.value; + let limbSize = Number(l); + let xBits = l0.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return xBits; + let yBits = l1.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return [...xBits, ...yBits]; + let zBits = l2.toBits(Math.min(length, limbSize)); + return [...xBits, ...yBits, ...zBits]; + } - // convenience methods - - /** - * Assert equality with a ForeignField-like value - * @example - * ```ts - * x.assertEquals(0, "x is zero"); - * ``` - */ - assertEquals(y: ForeignField | bigint | number, message?: string) { - try { - if (this.isConstant() && isConstant(y)) { - let x = this.toBigInt(); - let y0 = toFp(y); - if (x !== y0) { - throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); - } - return; - } - return Provable.assertEqual( - ForeignField.provable, - this, - ForeignField.from(y) - ); - } catch (err) { - throw withMessage(err, message); - } - } + /** + * Create a field element from its bits, as a `Bool[]` array. + * + * This method is provable! + */ + static fromBits(bits: Bool[]) { + let length = bits.length; + checkBitLength('ForeignField.fromBits()', length, this.sizeInBits); + 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)); + // note: due to the check on the number of bits, we know we return an "almost valid" field element + return this.from([l0, l1, l2]); + } - /** - * Assert that this field element is less than a constant c: `x < c`. - * - * The constant must satisfy `0 <= c < 2^264`, otherwise an error is thrown. - * - * @example - * ```ts - * x.assertLessThan(10); - * ``` - */ - assertLessThan(c: bigint | number, message?: string) { - assert( - c >= 0 && c < 1n << l3, - `ForeignField.assertLessThan(): expected c <= c < 2^264, got ${c}` - ); - try { - Gadgets.ForeignField.assertLessThan(this.value, toBigInt(c)); - } catch (err) { - throw withMessage(err, message); - } - } + // Provable - /** - * Check equality with a ForeignField-like value - * @example - * ```ts - * let isXZero = x.equals(0); - * ``` - */ - equals(y: ForeignField | bigint | number) { - if (this.isConstant() && isConstant(y)) { - return new Bool(this.toBigInt() === toFp(y)); - } - return Provable.equal(ForeignField.provable, this, ForeignField.from(y)); - } + static _provable: ProvablePure | undefined = undefined; - // bit packing - - /** - * Unpack a field element to its bits, as a {@link Bool}[] array. - * - * This method is provable! - */ - toBits(length = sizeInBits) { - checkBitLength('ForeignField.toBits()', length, sizeInBits); - let [l0, l1, l2] = this.value; - let limbSize = Number(l); - let xBits = l0.toBits(Math.min(length, limbSize)); - length -= limbSize; - if (length <= 0) return xBits; - let yBits = l1.toBits(Math.min(length, limbSize)); - length -= limbSize; - if (length <= 0) return [...xBits, ...yBits]; - let zBits = l2.toBits(Math.min(length, limbSize)); - return [...xBits, ...yBits, ...zBits]; - } + /** + * `Provable`, see {@link Provable} + */ + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } - /** - * Create a field element from its bits, as a `Bool[]` array. - * - * This method is provable! - */ - static fromBits(bits: Bool[]) { - let length = bits.length; - checkBitLength('ForeignField.fromBits()', length, sizeInBits); - 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)); - // note: due to the check on the number of bits, we know we return an "almost valid" field element - return ForeignField.from([l0, l1, l2]); - } + /** + * Instance version of `Provable.toFields`, see {@link Provable.toFields} + */ + toFields(): Field[] { + return this.value; + } +} - // Provable +function toBigInt(x: bigint | string | number | ForeignField) { + if (x instanceof ForeignField) return x.toBigInt(); + return BigInt(x); +} + +function isConstant(x: bigint | number | string | ForeignField) { + if (x instanceof ForeignField) return x.isConstant(); + return true; +} - /** - * `Provable`, see {@link Provable} - */ - static provable: ProvablePure = { +/** + * Create a class representing a prime order finite field, which is different from the native {@link Field}. + * + * ```ts + * const SmallField = createForeignField(17n); // the finite field F_17 + * ``` + * + * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. + * We support prime moduli up to a size of 259 bits. + * + * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), + * as well as helper methods like `assertEquals()` and `equals()`. + * + * _Advanced details:_ + * + * Internally, a foreign field element is represented as three native field elements, each of which + * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs + * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. + * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, + * see {@link ForeignField.assertAlmostFieldElement} for more details. + * If you need to prove that you have a fully reduced field element, use {@link ForeignField.assertCanonicalFieldElement}: + * + * ```ts + * x.assertCanonicalFieldElement(); // x < p + * ``` + * + * @param modulus the modulus of the finite field you are instantiating + */ +function createForeignField(modulus: bigint): typeof ForeignField { + assert( + modulus > 0n, + `ForeignField: modulus must be positive, got ${modulus}` + ); + assert( + modulus < foreignFieldMax, + `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` + ); + + return class ForeignField_ extends ForeignField { + static _modulus = modulus; + + static _provable = { toFields(x) { return x.value; }, @@ -372,41 +427,23 @@ function createForeignField(modulus: bigint) { }, fromFields(fields) { let limbs = TupleN.fromArray(3, fields); - return new ForeignField(limbs); + return new ForeignField_(limbs); }, /** * This performs the check in {@link ForeignField.assertAlmostFieldElement}. */ check(x: ForeignField) { - x.assertAlmostFieldElement(); + ForeignField_.assertAlmostFieldElement(x); }, }; - /** - * Instance version of `Provable.toFields`, see {@link Provable.toFields} - */ - toFields(): Field[] { - return this.value; - } - } - - function toBigInt(x: bigint | string | number | ForeignField) { - if (x instanceof ForeignField) return x.toBigInt(); - return BigInt(x); - } - function toFp(x: bigint | string | number | ForeignField) { - return mod(toBigInt(x), p); - } - function toLimbs(x: bigint | number | string | ForeignField): Field3 { - if (x instanceof ForeignField) return x.value; - return Field3.from(mod(BigInt(x), p)); - } - function isConstant(x: bigint | number | string | ForeignField) { - if (x instanceof ForeignField) return x.isConstant(); - return true; - } - - return ForeignField; + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(ForeignField_); + static assertAlmostFieldElement = + ForeignField.assertAlmostFieldElement.bind(ForeignField_); + static sum = ForeignField.sum.bind(ForeignField_); + static fromBits = ForeignField.fromBits.bind(ForeignField_); + }; } // the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus From 3bf1df14aa35a418cd22affa87bf8837cac929bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 14:38:32 +0100 Subject: [PATCH 0893/1215] add example --- src/examples/crypto/foreign-field.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/examples/crypto/foreign-field.ts diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts new file mode 100644 index 0000000000..d3a910e347 --- /dev/null +++ b/src/examples/crypto/foreign-field.ts @@ -0,0 +1,9 @@ +import { createForeignField } from 'o1js'; + +// toy example - F_17 + +class SmallField extends createForeignField(17n) {} + +let x = SmallField.from(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) From ff91e1ddc419d0ac79a4a219798e7e14cb4f4879 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 15:23:54 +0100 Subject: [PATCH 0894/1215] fix tests --- src/lib/gadgets/bitwise.unit-test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 90f863629a..e3444552a0 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -89,7 +89,6 @@ let Bitwise = ZkProgram({ }, }, }); - await Bitwise.compile(); [2, 4, 8, 16, 32, 64, 128].forEach((length) => { @@ -195,9 +194,8 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); -await equivalentAsync({ from: [field], to: field }, { runs: 3 })( +await equivalentAsync({ from: [uint(32)], to: uint(32) }, { runs: 30 })( (x) => { - if (x >= 2n ** 32n) throw Error('Does not fit into 32 bits'); return Fp.rot(x, 12n, 'left', 32n); }, async (x) => { @@ -219,7 +217,6 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( await equivalentAsync({ from: [field], to: field }, { runs: 3 })( (x) => { - console.log('input', x); if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); return Fp.leftShift(x, 12, 32); }, From a0f1e5ea4788272197c35569c873923ba9b41562 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 15:24:41 +0100 Subject: [PATCH 0895/1215] undo spacing in example --- src/examples/simple_zkapp.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/examples/simple_zkapp.ts b/src/examples/simple_zkapp.ts index 5ea4f94570..26f379770d 100644 --- a/src/examples/simple_zkapp.ts +++ b/src/examples/simple_zkapp.ts @@ -11,17 +11,22 @@ import { Bool, PublicKey, } from 'o1js'; - import { getProfiler } from './utils/profiler.js'; + const doProofs = true; + const beforeGenesis = UInt64.from(Date.now()); + class SimpleZkapp extends SmartContract { @state(Field) x = State(); + events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; + @method init() { super.init(); this.x.set(initialState); } + @method update(y: Field): Field { this.account.provedState.requireEquals(Bool(true)); this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); @@ -32,15 +37,18 @@ class SimpleZkapp extends SmartContract { this.x.set(newX); return newX; } + /** * This method allows a certain privileged account to claim half of the zkapp balance, but only once * @param caller the privileged account */ @method payout(caller: PrivateKey) { this.account.provedState.requireEquals(Bool(true)); + // check that caller is the privileged account let callerAddress = caller.toPublicKey(); callerAddress.assertEquals(privilegedAddress); + // assert that the caller account is new - this way, payout can only happen once let callerAccountUpdate = AccountUpdate.create(callerAddress); callerAccountUpdate.account.isNew.requireEquals(Bool(true)); @@ -49,34 +57,42 @@ class SimpleZkapp extends SmartContract { this.account.balance.requireEquals(balance); let halfBalance = balance.div(2); this.send({ to: callerAccountUpdate, amount: halfBalance }); + // emit some events this.emitEvent('payoutReceiver', callerAddress); this.emitEvent('payout', halfBalance); } } + const SimpleProfiler = getProfiler('Simple zkApp'); SimpleProfiler.start('Simple zkApp test flow'); let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); Mina.setActiveInstance(Local); + // a test account that pays all the fees, and puts additional funds into the zkapp let { privateKey: senderKey, publicKey: sender } = Local.testAccounts[0]; + // the zkapp account let zkappKey = PrivateKey.random(); let zkappAddress = zkappKey.toPublicKey(); + // a special account that is allowed to pull out half of the zkapp balance, once let privilegedKey = PrivateKey.fromBase58( 'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep' ); let privilegedAddress = privilegedKey.toPublicKey(); + let initialBalance = 10_000_000_000; let initialState = Field(1); let zkapp = new SimpleZkapp(zkappAddress); + if (doProofs) { console.log('compile'); console.time('compile'); await SimpleZkapp.compile(); console.timeEnd('compile'); } + console.log('deploy'); let tx = await Mina.transaction(sender, () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); @@ -85,16 +101,20 @@ let tx = await Mina.transaction(sender, () => { }); await tx.prove(); await tx.sign([senderKey]).send(); + console.log('initial state: ' + zkapp.x.get()); console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); + let account = Mina.getAccount(zkappAddress); console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); + console.log('update'); tx = await Mina.transaction(sender, () => { zkapp.update(Field(3)); }); await tx.prove(); await tx.sign([senderKey]).send(); + // pay more into the zkapp -- this doesn't need a proof console.log('receive'); tx = await Mina.transaction(sender, () => { @@ -102,6 +122,7 @@ tx = await Mina.transaction(sender, () => { payerAccountUpdate.send({ to: zkappAddress, amount: UInt64.from(8e9) }); }); await tx.sign([senderKey]).send(); + console.log('payout'); tx = await Mina.transaction(sender, () => { AccountUpdate.fundNewAccount(sender); @@ -110,8 +131,10 @@ tx = await Mina.transaction(sender, () => { await tx.prove(); await tx.sign([senderKey]).send(); sender; + console.log('final state: ' + zkapp.x.get()); console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); + console.log('try to payout a second time..'); tx = await Mina.transaction(sender, () => { zkapp.payout(privilegedKey); @@ -122,6 +145,7 @@ try { } catch (err: any) { console.log('Transaction failed with error', err.message); } + console.log('try to payout to a different account..'); try { tx = await Mina.transaction(sender, () => { @@ -132,9 +156,11 @@ try { } catch (err: any) { console.log('Transaction failed with error', err.message); } + console.log( `should still be the same final balance: ${zkapp.account.balance .get() .div(1e9)} MINA` ); + SimpleProfiler.stop().store(); From de717971e6521fc4a20080a215c0333a0a40f3fb Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 15:45:20 +0100 Subject: [PATCH 0896/1215] fix compile issue --- src/lib/gadgets/range-check.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index c090c483ef..4009800492 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -262,11 +262,11 @@ function rangeCheckHelper(length: number, x: Field) { */ function rangeCheckN(n: number, x: Field, message: string = '') { assert( - length <= Fp.sizeInBits, - `bit length must be ${Fp.sizeInBits} or less, got ${length}` + n <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${n}` ); - assert(length > 0, `bit length must be positive, got ${length}`); - assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + assert(n > 0, `bit length must be positive, got ${n}`); + assert(n % 16 === 0, '`length` has to be a multiple of 16.'); if (x.isConstant()) { if (x.toBigInt() >= 1n << BigInt(n)) { @@ -286,11 +286,11 @@ function rangeCheckN(n: number, x: Field, message: string = '') { */ function isInRangeN(n: number, x: Field) { assert( - length <= Fp.sizeInBits, - `bit length must be ${Fp.sizeInBits} or less, got ${length}` + n <= Fp.sizeInBits, + `bit length must be ${Fp.sizeInBits} or less, got ${n}` ); - assert(length > 0, `bit length must be positive, got ${length}`); - assert(length % 16 === 0, '`length` has to be a multiple of 16.'); + assert(n > 0, `bit length must be positive, got ${n}`); + assert(n % 16 === 0, '`length` has to be a multiple of 16.'); if (x.isConstant()) { return new Bool(x.toBigInt() < 1n << BigInt(n)); From c8781aae56ff5705fae8d3bd34c53ad49c3393f7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 16:11:44 +0100 Subject: [PATCH 0897/1215] introduce different classes representing different range checks --- src/lib/foreign-field.ts | 247 ++++++++++++++++++++--------- src/lib/foreign-field.unit-test.ts | 29 +++- 2 files changed, 196 insertions(+), 80 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index a50a504aac..b4d7245740 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -11,7 +11,12 @@ import { l3, l } from './gadgets/range-check.js'; // external API export { createForeignField }; -export type { ForeignField }; +export type { + ForeignField, + UnreducedForeignField, + AlmostForeignField, + CanonicalForeignField, +}; class ForeignField { static _modulus: bigint | undefined = undefined; @@ -33,6 +38,34 @@ class ForeignField { */ value: Field3; + private get Class() { + return this.constructor as typeof ForeignField; + } + + /** + * Sibling classes that represent different ranges of field elements. + */ + static _variants: + | { + unreduced: typeof UnreducedForeignField; + almostReduced: typeof AlmostForeignField; + canonical: typeof CanonicalForeignField; + } + | undefined = undefined; + + static get Unreduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.unreduced; + } + static get AlmostReduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.almostReduced; + } + static get Canonical() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.canonical; + } + /** * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. * @example @@ -59,16 +92,12 @@ class ForeignField { if (x instanceof ForeignField) return x.value; return Field3.from(mod(BigInt(x), this.modulus)); } - private get class() { - return this.constructor as typeof ForeignField; - } /** * Coerce the input to a {@link ForeignField}. */ - static from(x: ForeignField | Field3 | bigint | number | string) { - if (x instanceof ForeignField) return x; - return new this(x); + static from(x: bigint | number | string): CanonicalForeignField { + return new this(x) as CanonicalForeignField; } /** @@ -90,7 +119,7 @@ class ForeignField { */ toConstant(): ForeignField { let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); - return new this.class(constantLimbs); + return new this.Class(constantLimbs); } /** @@ -104,27 +133,26 @@ class ForeignField { * Assert that this field element lies in the range [0, 2^k), * where k = ceil(log2(p)) and p is the foreign field modulus. * - * **Warning**: This check is added to all `ForeignField` elements by default. - * You don't have to use it. - * * Note: this does not ensure that the field elements is in the canonical range [0, p). * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. * - * We use the weaker property by default because it is cheaper to prove and sufficient for + * You should typically use the weaker property because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ - static assertAlmostFieldElement(x: ForeignField) { + assertAlmostFieldElement(): asserts this is AlmostForeignField { // TODO: this is not very efficient, but the only way to abstract away the complicated // range check assumptions and also not introduce a global context of pending range checks. // we plan to get rid of bounds checks anyway, then this is just a multi-range check - Gadgets.ForeignField.assertAlmostFieldElements([x.value], this.modulus); + Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, { + skipMrc: true, + }); } /** * Assert that this field element is fully reduced, * i.e. lies in the range [0, p), where p is the foreign field modulus. */ - assertCanonicalFieldElement() { + assertCanonicalFieldElement(): asserts this is CanonicalForeignField { const p = this.modulus; this.assertLessThan(p); } @@ -139,7 +167,7 @@ class ForeignField { * ``` */ add(y: ForeignField | bigint | number) { - return this.class.sum([this, y], [1]); + return this.Class.sum([this, y], [1]); } /** @@ -150,7 +178,8 @@ class ForeignField { * ``` */ neg() { - return this.class.sum([this.class.from(0n), this], [-1]); + let zero: ForeignField = this.Class.from(0n); + return this.Class.sum([zero, this], [-1]); } /** @@ -161,7 +190,7 @@ class ForeignField { * ``` */ sub(y: ForeignField | bigint | number) { - return this.class.sum([this, y], [-1]); + return this.Class.sum([this, y], [-1]); } /** @@ -190,9 +219,7 @@ class ForeignField { let fields = xs.map((x) => this.toLimbs(x)); let ops = operations.map((op) => (op === 1 ? 1n : -1n)); let z = Gadgets.ForeignField.sum(fields, ops, p); - // TODO inefficient: add bound check on the result - Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); - return new this(z); + return new this.Unreduced(z); } /** @@ -202,12 +229,10 @@ class ForeignField { * x.mul(y); // x*y mod p * ``` */ - mul(y: ForeignField | bigint | number) { + mul(y: AlmostForeignField | bigint | number) { const p = this.modulus; - let z = Gadgets.ForeignField.mul(this.value, this.class.toLimbs(y), p); - // TODO inefficient: add bound check on the result - Gadgets.ForeignField.assertAlmostFieldElements([z], p, { skipMrc: true }); - return new this.class(z); + let z = Gadgets.ForeignField.mul(this.value, this.Class.toLimbs(y), p); + return new this.Class.Unreduced(z); } /** @@ -218,10 +243,10 @@ class ForeignField { * z.mul(x).assertEquals(1); * ``` */ - inv(): ForeignField { + inv() { const p = this.modulus; let z = Gadgets.ForeignField.inv(this.value, p); - return new this.class(z); + return new this.Class.AlmostReduced(z); } /** @@ -234,8 +259,8 @@ class ForeignField { */ div(y: ForeignField | bigint | number) { const p = this.modulus; - let z = Gadgets.ForeignField.div(this.value, this.class.toLimbs(y), p); - return new this.class(z); + let z = Gadgets.ForeignField.div(this.value, this.Class.toLimbs(y), p); + return new this.Class.AlmostReduced(z); } // convenience methods @@ -258,11 +283,7 @@ class ForeignField { } return; } - return Provable.assertEqual( - this.class.provable, - this, - this.class.from(y) - ); + return Provable.assertEqual(this.Class.provable, this, new this.Class(y)); } catch (err) { throw withMessage(err, message); } @@ -302,7 +323,7 @@ class ForeignField { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() === mod(toBigInt(y), p)); } - return Provable.equal(this.class.provable, this, this.class.from(y)); + return Provable.equal(this.Class.provable, this, new this.Class(y)); } // bit packing @@ -313,7 +334,7 @@ class ForeignField { * This method is provable! */ toBits(length?: number) { - const sizeInBits = this.class.sizeInBits; + const sizeInBits = this.Class.sizeInBits; if (length === undefined) length = sizeInBits; checkBitLength('ForeignField.toBits()', length, sizeInBits); let [l0, l1, l2] = this.value; @@ -341,12 +362,21 @@ class ForeignField { let l1 = Field.fromBits(bits.slice(1 * limbSize, 2 * limbSize)); let l2 = Field.fromBits(bits.slice(2 * limbSize, 3 * limbSize)); // note: due to the check on the number of bits, we know we return an "almost valid" field element - return this.from([l0, l1, l2]); + return new this([l0, l1, l2]) as AlmostForeignField; + } + + /** + * Instance version of `Provable.toFields`, see {@link Provable.toFields} + */ + toFields(): Field[] { + return this.value; } - // Provable + static check(_: ForeignField) { + throw Error('ForeignField.check() not implemented: must use a subclass'); + } - static _provable: ProvablePure | undefined = undefined; + static _provable: any = undefined; /** * `Provable`, see {@link Provable} @@ -355,12 +385,49 @@ class ForeignField { assert(this._provable !== undefined, 'ForeignField class not initialized.'); return this._provable; } +} - /** - * Instance version of `Provable.toFields`, see {@link Provable.toFields} - */ - toFields(): Field[] { - return this.value; +class UnreducedForeignField extends ForeignField { + type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; + + static _provable: ProvablePure | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField): asserts x is UnreducedForeignField { + Gadgets.multiRangeCheck(x.value); + } +} + +class AlmostForeignField extends ForeignField { + type: 'AlmostReduced' | 'FullyReduced' = 'AlmostReduced'; + + static _provable: ProvablePure | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField): asserts x is AlmostForeignField { + Gadgets.multiRangeCheck(x.value); + x.assertAlmostFieldElement(); + } +} + +class CanonicalForeignField extends ForeignField { + type = 'FullyReduced' as const; + + static _provable: ProvablePure | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField): asserts x is CanonicalForeignField { + Gadgets.multiRangeCheck(x.value); + x.assertCanonicalFieldElement(); } } @@ -412,38 +479,47 @@ function createForeignField(modulus: bigint): typeof ForeignField { `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` ); - return class ForeignField_ extends ForeignField { + class UnreducedField extends UnreducedForeignField { static _modulus = modulus; + static _provable = provable(UnreducedField); - static _provable = { - toFields(x) { - return x.value; - }, - toAuxiliary(): [] { - return []; - }, - sizeInFields() { - return 3; - }, - fromFields(fields) { - let limbs = TupleN.fromArray(3, fields); - return new ForeignField_(limbs); - }, - /** - * This performs the check in {@link ForeignField.assertAlmostFieldElement}. - */ - check(x: ForeignField) { - ForeignField_.assertAlmostFieldElement(x); - }, - }; + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(UnreducedField); + static sum = ForeignField.sum.bind(UnreducedField); + static fromBits = ForeignField.fromBits.bind(UnreducedField); + } + + class AlmostField extends AlmostForeignField { + static _modulus = modulus; + static _provable = provable(AlmostField); // bind public static methods to the class so that they have `this` defined - static from = ForeignField.from.bind(ForeignField_); - static assertAlmostFieldElement = - ForeignField.assertAlmostFieldElement.bind(ForeignField_); - static sum = ForeignField.sum.bind(ForeignField_); - static fromBits = ForeignField.fromBits.bind(ForeignField_); + static from = ForeignField.from.bind(AlmostField); + static sum = ForeignField.sum.bind(AlmostField); + static fromBits = ForeignField.fromBits.bind(AlmostField); + } + + class CanonicalField extends CanonicalForeignField { + static _modulus = modulus; + static _provable = provable(CanonicalField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(CanonicalField); + static sum = ForeignField.sum.bind(CanonicalField); + static fromBits = ForeignField.fromBits.bind(CanonicalField); + } + + let variants = { + unreduced: UnreducedField, + almostReduced: AlmostField, + canonical: CanonicalField, }; + + UnreducedField._variants = variants; + AlmostField._variants = variants; + CanonicalField._variants = variants; + + return UnreducedField; } // the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus @@ -452,3 +528,30 @@ function createForeignField(modulus: bigint): typeof ForeignField { // f_max >= sqrt(2^254 * 2^264) = 2^259 const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * l) / 2n; const foreignFieldMax = 1n << foreignFieldMaxBits; + +// provable + +type Constructor = new (...args: any[]) => T; + +function provable( + Class: Constructor & { check(x: ForeignField): asserts x is F } +): ProvablePure { + return { + toFields(x) { + return x.value; + }, + toAuxiliary(): [] { + return []; + }, + sizeInFields() { + return 3; + }, + fromFields(fields) { + let limbs = TupleN.fromArray(3, fields); + return new Class(limbs); + }, + check(x: ForeignField): asserts x is F { + Class.check(x); + }, + }; +} diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 3fcedf8969..9522c5c223 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,6 +1,11 @@ import { ProvablePure } from '../snarky.js'; import { Field, Group } from './core.js'; -import { ForeignField, createForeignField } from './foreign-field.js'; +import { + AlmostForeignField, + ForeignField, + UnreducedForeignField, + createForeignField, +} from './foreign-field.js'; import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { @@ -20,8 +25,10 @@ import { assert } from './gadgets/common.js'; // toy example - F_17 class SmallField extends createForeignField(17n) {} -let x = new SmallField(16); + +let x: SmallField = new SmallField(16); x.assertEquals(-1); // 16 = -1 (mod 17) +x.assertAlmostFieldElement(); x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) // invalid example - modulus too large @@ -54,18 +61,24 @@ test(Random.scalar, (x0, assert) => { // test equivalence of in-SNARK and out-of-SNARK operations -let f: ProvableSpec = { +let f: ProvableSpec = { rng: Random.scalar, there: ForeignScalar.from, back: (x) => x.toBigInt(), - provable: ForeignScalar.provable, + provable: ForeignScalar.AlmostReduced.provable, +}; +let big264: ProvableSpec = { + rng: Random.bignat(1n << 264n), + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.Unreduced.provable, }; // arithmetic -equivalent({ from: [f, f], to: f })(Fq.add, (x, y) => x.add(y)); -equivalent({ from: [f, f], to: f })(Fq.sub, (x, y) => x.sub(y)); -equivalent({ from: [f], to: f })(Fq.negate, (x) => x.neg()); -equivalent({ from: [f, f], to: f })(Fq.mul, (x, y) => x.mul(y)); +equivalent({ from: [f, f], to: big264 })(Fq.add, (x, y) => x.add(y)); +equivalent({ from: [f, f], to: big264 })(Fq.sub, (x, y) => x.sub(y)); +equivalent({ from: [f], to: big264 })(Fq.negate, (x) => x.neg()); +equivalent({ from: [f, f], to: big264 })(Fq.mul, (x, y) => x.mul(y)); equivalent({ from: [f], to: f })( (x) => Fq.inverse(x) ?? throwError('division by 0'), (x) => x.inv() From be1d36fa7d7a3833e2e9e7d0a4abeb70dfe4acdc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 16:36:46 +0100 Subject: [PATCH 0898/1215] move multiplication away from unreduced class --- src/lib/foreign-field.ts | 134 +++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index b4d7245740..067dbc4636 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -38,7 +38,7 @@ class ForeignField { */ value: Field3; - private get Class() { + get Constructor() { return this.constructor as typeof ForeignField; } @@ -119,7 +119,7 @@ class ForeignField { */ toConstant(): ForeignField { let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); - return new this.Class(constantLimbs); + return new this.Constructor(constantLimbs); } /** @@ -167,7 +167,7 @@ class ForeignField { * ``` */ add(y: ForeignField | bigint | number) { - return this.Class.sum([this, y], [1]); + return this.Constructor.sum([this, y], [1]); } /** @@ -178,8 +178,8 @@ class ForeignField { * ``` */ neg() { - let zero: ForeignField = this.Class.from(0n); - return this.Class.sum([zero, this], [-1]); + let zero: ForeignField = this.Constructor.from(0n); + return this.Constructor.sum([zero, this], [-1]); } /** @@ -190,7 +190,7 @@ class ForeignField { * ``` */ sub(y: ForeignField | bigint | number) { - return this.Class.sum([this, y], [-1]); + return this.Constructor.sum([this, y], [-1]); } /** @@ -222,47 +222,6 @@ class ForeignField { return new this.Unreduced(z); } - /** - * Finite field multiplication - * @example - * ```ts - * x.mul(y); // x*y mod p - * ``` - */ - mul(y: AlmostForeignField | bigint | number) { - const p = this.modulus; - let z = Gadgets.ForeignField.mul(this.value, this.Class.toLimbs(y), p); - return new this.Class.Unreduced(z); - } - - /** - * Multiplicative inverse in the finite field - * @example - * ```ts - * let z = x.inv(); // 1/x mod p - * z.mul(x).assertEquals(1); - * ``` - */ - inv() { - const p = this.modulus; - let z = Gadgets.ForeignField.inv(this.value, p); - return new this.Class.AlmostReduced(z); - } - - /** - * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. - * @example - * ```ts - * let z = x.div(y); // x/y mod p - * z.mul(y).assertEquals(x); - * ``` - */ - div(y: ForeignField | bigint | number) { - const p = this.modulus; - let z = Gadgets.ForeignField.div(this.value, this.Class.toLimbs(y), p); - return new this.Class.AlmostReduced(z); - } - // convenience methods /** @@ -283,7 +242,11 @@ class ForeignField { } return; } - return Provable.assertEqual(this.Class.provable, this, new this.Class(y)); + return Provable.assertEqual( + this.Constructor.provable, + this, + new this.Constructor(y) + ); } catch (err) { throw withMessage(err, message); } @@ -323,7 +286,11 @@ class ForeignField { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() === mod(toBigInt(y), p)); } - return Provable.equal(this.Class.provable, this, new this.Class(y)); + return Provable.equal( + this.Constructor.provable, + this, + new this.Constructor(y) + ); } // bit packing @@ -334,7 +301,7 @@ class ForeignField { * This method is provable! */ toBits(length?: number) { - const sizeInBits = this.Class.sizeInBits; + const sizeInBits = this.Constructor.sizeInBits; if (length === undefined) length = sizeInBits; checkBitLength('ForeignField.toBits()', length, sizeInBits); let [l0, l1, l2] = this.value; @@ -387,6 +354,49 @@ class ForeignField { } } +class ForeignFieldWithMul extends ForeignField { + /** + * Finite field multiplication + * @example + * ```ts + * x.mul(y); // x*y mod p + * ``` + */ + mul(y: AlmostForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.mul(this.value, toLimbs(y, p), p); + return new this.Constructor.Unreduced(z); + } + + /** + * Multiplicative inverse in the finite field + * @example + * ```ts + * let z = x.inv(); // 1/x mod p + * z.mul(x).assertEquals(1); + * ``` + */ + inv() { + const p = this.modulus; + let z = Gadgets.ForeignField.inv(this.value, p); + return new this.Constructor.AlmostReduced(z); + } + + /** + * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. + * @example + * ```ts + * let z = x.div(y); // x/y mod p + * z.mul(y).assertEquals(x); + * ``` + */ + div(y: ForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.div(this.value, toLimbs(y, p), p); + return new this.Constructor.AlmostReduced(z); + } +} + class UnreducedForeignField extends ForeignField { type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; @@ -401,9 +411,13 @@ class UnreducedForeignField extends ForeignField { } } -class AlmostForeignField extends ForeignField { +class AlmostForeignField extends ForeignFieldWithMul { type: 'AlmostReduced' | 'FullyReduced' = 'AlmostReduced'; + constructor(x: AlmostForeignField | Field3 | bigint | number | string) { + super(x); + } + static _provable: ProvablePure | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); @@ -416,9 +430,13 @@ class AlmostForeignField extends ForeignField { } } -class CanonicalForeignField extends ForeignField { +class CanonicalForeignField extends ForeignFieldWithMul { type = 'FullyReduced' as const; + constructor(x: CanonicalForeignField | Field3 | bigint | number | string) { + super(x); + } + static _provable: ProvablePure | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); @@ -431,6 +449,14 @@ class CanonicalForeignField extends ForeignField { } } +function toLimbs( + x: bigint | number | string | ForeignField, + p: bigint +): Field3 { + if (x instanceof ForeignField) return x.value; + return Field3.from(mod(BigInt(x), p)); +} + function toBigInt(x: bigint | string | number | ForeignField) { if (x instanceof ForeignField) return x.toBigInt(); return BigInt(x); @@ -469,7 +495,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * * @param modulus the modulus of the finite field you are instantiating */ -function createForeignField(modulus: bigint): typeof ForeignField { +function createForeignField(modulus: bigint): typeof AlmostForeignField { assert( modulus > 0n, `ForeignField: modulus must be positive, got ${modulus}` @@ -519,7 +545,7 @@ function createForeignField(modulus: bigint): typeof ForeignField { AlmostField._variants = variants; CanonicalField._variants = variants; - return UnreducedField; + return AlmostField; } // the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus From b6850c9a20ea4c9dcc121d2ac5c5ef3ba7bb357c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 16:48:22 +0100 Subject: [PATCH 0899/1215] return the correct values from assertions --- src/lib/foreign-field.ts | 21 +++++++++++---------- src/lib/foreign-field.unit-test.ts | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 067dbc4636..efb63e85bb 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -139,22 +139,24 @@ class ForeignField { * You should typically use the weaker property because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ - assertAlmostFieldElement(): asserts this is AlmostForeignField { + assertAlmostFieldElement() { // TODO: this is not very efficient, but the only way to abstract away the complicated // range check assumptions and also not introduce a global context of pending range checks. // we plan to get rid of bounds checks anyway, then this is just a multi-range check Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, { skipMrc: true, }); + return new this.Constructor.AlmostReduced(this.value); } /** * Assert that this field element is fully reduced, * i.e. lies in the range [0, p), where p is the foreign field modulus. */ - assertCanonicalFieldElement(): asserts this is CanonicalForeignField { + assertCanonicalFieldElement() { const p = this.modulus; this.assertLessThan(p); + return new this.Constructor.Canonical(this.value); } // arithmetic with full constraints, for safe use @@ -406,7 +408,7 @@ class UnreducedForeignField extends ForeignField { return this._provable; } - static check(x: ForeignField): asserts x is UnreducedForeignField { + static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); } } @@ -424,7 +426,7 @@ class AlmostForeignField extends ForeignFieldWithMul { return this._provable; } - static check(x: ForeignField): asserts x is AlmostForeignField { + static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); x.assertAlmostFieldElement(); } @@ -443,7 +445,7 @@ class CanonicalForeignField extends ForeignFieldWithMul { return this._provable; } - static check(x: ForeignField): asserts x is CanonicalForeignField { + static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); x.assertCanonicalFieldElement(); } @@ -495,7 +497,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * * @param modulus the modulus of the finite field you are instantiating */ -function createForeignField(modulus: bigint): typeof AlmostForeignField { +function createForeignField(modulus: bigint): typeof ForeignField { assert( modulus > 0n, `ForeignField: modulus must be positive, got ${modulus}` @@ -540,12 +542,11 @@ function createForeignField(modulus: bigint): typeof AlmostForeignField { almostReduced: AlmostField, canonical: CanonicalField, }; - UnreducedField._variants = variants; AlmostField._variants = variants; CanonicalField._variants = variants; - return AlmostField; + return UnreducedField; } // the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus @@ -560,7 +561,7 @@ const foreignFieldMax = 1n << foreignFieldMaxBits; type Constructor = new (...args: any[]) => T; function provable( - Class: Constructor & { check(x: ForeignField): asserts x is F } + Class: Constructor & { check(x: ForeignField): void } ): ProvablePure { return { toFields(x) { @@ -576,7 +577,7 @@ function provable( let limbs = TupleN.fromArray(3, fields); return new Class(limbs); }, - check(x: ForeignField): asserts x is F { + check(x: ForeignField) { Class.check(x); }, }; diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 9522c5c223..3d62b46900 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -26,9 +26,8 @@ import { assert } from './gadgets/common.js'; class SmallField extends createForeignField(17n) {} -let x: SmallField = new SmallField(16); +let x = new SmallField(16).assertAlmostFieldElement(); x.assertEquals(-1); // 16 = -1 (mod 17) -x.assertAlmostFieldElement(); x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) // invalid example - modulus too large @@ -114,7 +113,8 @@ let scalarShift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; function unshift(s: ForeignField) { - return s.sub(scalarShift).mul(oneHalf); + let sMinusShift = s.sub(scalarShift).assertAlmostFieldElement(); + return sMinusShift.mul(oneHalf); } function scaleShifted(point: Group, shiftedScalar: Scalar) { let oneHalfGroup = point.scale(oneHalf); From e0743c1542dcbc7408b6f4679ec19a2becb5243c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 17:20:54 +0100 Subject: [PATCH 0900/1215] add more docs --- src/lib/foreign-field.ts | 82 ++++++++++++++++++++++++------ src/lib/foreign-field.unit-test.ts | 5 +- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index efb63e85bb..56beca23d8 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -96,8 +96,10 @@ class ForeignField { /** * Coerce the input to a {@link ForeignField}. */ - static from(x: bigint | number | string): CanonicalForeignField { - return new this(x) as CanonicalForeignField; + static from(x: bigint | number | string): CanonicalForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField { + if (x instanceof ForeignField) return x; + return new this.Canonical(x); } /** @@ -133,13 +135,14 @@ class ForeignField { * Assert that this field element lies in the range [0, 2^k), * where k = ceil(log2(p)) and p is the foreign field modulus. * - * Note: this does not ensure that the field elements is in the canonical range [0, p). - * To assert that stronger property, use {@link ForeignField.assertCanonicalFieldElement}. + * Returns the field element as a {@link AlmostForeignField}. * - * You should typically use the weaker property because it is cheaper to prove and sufficient for + * Note: this does not ensure that the field elements is in the canonical range [0, p). + * To assert that stronger property, there is {@link ForeignField.assertCanonicalFieldElement}. + * You should typically use {@link ForeignField.assertAlmostReduced} though, because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ - assertAlmostFieldElement() { + assertAlmostReduced() { // TODO: this is not very efficient, but the only way to abstract away the complicated // range check assumptions and also not introduce a global context of pending range checks. // we plan to get rid of bounds checks anyway, then this is just a multi-range check @@ -152,10 +155,11 @@ class ForeignField { /** * Assert that this field element is fully reduced, * i.e. lies in the range [0, p), where p is the foreign field modulus. + * + * Returns the field element as a {@link CanonicalForeignField}. */ assertCanonicalFieldElement() { - const p = this.modulus; - this.assertLessThan(p); + this.assertLessThan(this.modulus); return new this.Constructor.Canonical(this.value); } @@ -228,12 +232,31 @@ class ForeignField { /** * Assert equality with a ForeignField-like value + * * @example * ```ts * x.assertEquals(0, "x is zero"); * ``` + * + * Since asserting equality can also serve as a range check, + * this method returns `x` with the appropriate type: + * + * @example + * ```ts + * let xChecked = x.assertEquals(1, "x is 1"); + * xChecked satisfies CanonicalForeignField; + * ``` */ - assertEquals(y: ForeignField | bigint | number, message?: string) { + assertEquals( + y: bigint | number | CanonicalForeignField, + message?: string + ): CanonicalForeignField; + assertEquals(y: AlmostForeignField, message?: string): AlmostForeignField; + assertEquals(y: ForeignField, message?: string): ForeignField; + assertEquals( + y: ForeignField | bigint | number, + message?: string + ): ForeignField { const p = this.modulus; try { if (this.isConstant() && isConstant(y)) { @@ -242,13 +265,18 @@ class ForeignField { if (x !== y0) { throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); } - return; + return new this.Constructor.AlmostReduced(this.value); } - return Provable.assertEqual( + Provable.assertEqual( this.Constructor.provable, this, new this.Constructor(y) ); + if (isConstant(y) || y instanceof ForeignFieldWithMul) { + return new this.Constructor.AlmostReduced(this.value); + } else { + return this; + } } catch (err) { throw withMessage(err, message); } @@ -428,7 +456,7 @@ class AlmostForeignField extends ForeignFieldWithMul { static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); - x.assertAlmostFieldElement(); + x.assertAlmostReduced(); } } @@ -472,6 +500,7 @@ function isConstant(x: bigint | number | string | ForeignField) { /** * Create a class representing a prime order finite field, which is different from the native {@link Field}. * + * @example * ```ts * const SmallField = createForeignField(17n); // the finite field F_17 * ``` @@ -487,13 +516,36 @@ function isConstant(x: bigint | number | string | ForeignField) { * Internally, a foreign field element is represented as three native field elements, each of which * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. + * * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, - * see {@link ForeignField.assertAlmostFieldElement} for more details. - * If you need to prove that you have a fully reduced field element, use {@link ForeignField.assertCanonicalFieldElement}: + * see {@link ForeignField.assertAlmostReduced} for more details. + * + * This weaker assumption is what we call "almost reduced", and it is represented by the {@link AlmostForeignField} class. + * Note that only {@link AlmostForeignField} supports multiplication and inversion, while {@link UnreducedForeignField} + * only supports addition and subtraction. + * + * This function returns the `Unreduced` class, which will cause the minimum amount of range checks to be created by default. + * If you want to do multiplication, you have two options: + * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. + * @example + * ```ts + * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => new ForeignField.AlmostReduced(5)); + * ``` + * - create your field elements normally and convert them using `x.assertAlmostReduced()`. + * @example + * ```ts + * let xChecked = x.assertAlmostReduced(); // asserts x < 2^ceil(log2(p)); returns `AlmostForeignField` + * ``` + * + * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced / "canonical" field elements. + * To convert to a canonical field element, use {@link ForeignField.assertCanonicalFieldElement}: * * ```ts - * x.assertCanonicalFieldElement(); // x < p + * x.assertCanonicalFieldElement(); // asserts x < p; returns `CanonicalForeignField` * ``` + * You will likely not need `CanonicalForeignField`, except possibly for creating your own interfaces which expect fully reduced field elements. + * + * Base types for all of these classes are separately exported as {@link UnreducedForeignField}, {@link AlmostForeignField} and {@link CanonicalForeignField}., * * @param modulus the modulus of the finite field you are instantiating */ diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 3d62b46900..9ef6617d6e 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -26,7 +26,7 @@ import { assert } from './gadgets/common.js'; class SmallField extends createForeignField(17n) {} -let x = new SmallField(16).assertAlmostFieldElement(); +let x = SmallField.from(16); x.assertEquals(-1); // 16 = -1 (mod 17) x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) @@ -113,8 +113,7 @@ let scalarShift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; function unshift(s: ForeignField) { - let sMinusShift = s.sub(scalarShift).assertAlmostFieldElement(); - return sMinusShift.mul(oneHalf); + return s.sub(scalarShift).assertAlmostReduced().mul(oneHalf); } function scaleShifted(point: Group, shiftedScalar: Scalar) { let oneHalfGroup = point.scale(oneHalf); From 6bb3104e645d560e7b62df9d18b80f145530bd1f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 18:03:25 +0100 Subject: [PATCH 0901/1215] efficient bounds check --- src/lib/foreign-field.ts | 25 ++++++++++++++++++++++--- src/lib/util/types.ts | 12 +++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 56beca23d8..6699b269f0 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -3,7 +3,7 @@ import { mod, Fp } from '../bindings/crypto/finite_field.js'; import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; -import { Tuple, TupleN } from './util/types.js'; +import { Tuple, TupleMap, TupleN } from './util/types.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './gadgets/common.js'; @@ -137,9 +137,11 @@ class ForeignField { * * Returns the field element as a {@link AlmostForeignField}. * + * For a more efficient version of this for multiple field elements, see {@link assertAlmostReduced}. + * * Note: this does not ensure that the field elements is in the canonical range [0, p). - * To assert that stronger property, there is {@link ForeignField.assertCanonicalFieldElement}. - * You should typically use {@link ForeignField.assertAlmostReduced} though, because it is cheaper to prove and sufficient for + * To assert that stronger property, there is {@link assertCanonicalFieldElement}. + * You should typically use {@link assertAlmostReduced} though, because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ assertAlmostReduced() { @@ -152,6 +154,23 @@ class ForeignField { return new this.Constructor.AlmostReduced(this.value); } + /** + * Assert that one or more field elements lie in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * This is most efficient than when checking a multiple of 3 field elements at once. + */ + static assertAlmostReduced>( + ...xs: T + ): TupleMap { + Gadgets.ForeignField.assertAlmostFieldElements( + xs.map((x) => x.value), + this.modulus, + { skipMrc: true } + ); + return Tuple.map(xs, (x) => new this.AlmostReduced(x.value)); + } + /** * Assert that this field element is fully reduced, * i.e. lies in the range [0, p), where p is the foreign field modulus. diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index f5fa8fcc10..79cc38d08c 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -1,14 +1,20 @@ import { assert } from '../errors.js'; -export { Tuple, TupleN }; +export { Tuple, TupleN, TupleMap }; type Tuple = [T, ...T[]] | []; +type TupleMap, B> = [ + ...{ + [i in keyof T]: B; + } +]; + const Tuple = { map, B>( tuple: T, f: (a: T[number]) => B - ): [...{ [i in keyof T]: B }] { + ): TupleMap { return tuple.map(f) as any; }, }; @@ -26,7 +32,7 @@ const TupleN = { map, B>( tuple: T, f: (a: T[number]) => B - ): [...{ [i in keyof T]: B }] { + ): TupleMap { return tuple.map(f) as any; }, From b8896a098925bb8da18764d6e603b412e285a205 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 18:10:21 +0100 Subject: [PATCH 0902/1215] a few more comments --- src/lib/foreign-field.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 6699b269f0..ddec83e6ea 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -53,14 +53,23 @@ class ForeignField { } | undefined = undefined; + /** + * Constructor for unreduced field elements. + */ static get Unreduced() { assert(this._variants !== undefined, 'ForeignField class not initialized.'); return this._variants.unreduced; } + /** + * Constructor for field elements that are "almost reduced", i.e. lie in the range [0, 2^ceil(log2(p))). + */ static get AlmostReduced() { assert(this._variants !== undefined, 'ForeignField class not initialized.'); return this._variants.almostReduced; } + /** + * Constructor for field elements that are fully reduced, i.e. lie in the range [0, p). + */ static get Canonical() { assert(this._variants !== undefined, 'ForeignField class not initialized.'); return this._variants.canonical; @@ -548,7 +557,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. * @example * ```ts - * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => new ForeignField.AlmostReduced(5)); + * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => ForeignField.from(5)); * ``` * - create your field elements normally and convert them using `x.assertAlmostReduced()`. * @example @@ -557,7 +566,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * ``` * * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced / "canonical" field elements. - * To convert to a canonical field element, use {@link ForeignField.assertCanonicalFieldElement}: + * To convert to a canonical field element, use {@link assertCanonicalFieldElement}: * * ```ts * x.assertCanonicalFieldElement(); // asserts x < p; returns `CanonicalForeignField` From fb2aabdb8cbf88428df923032b77f28b53e47bf5 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 19:25:13 +0100 Subject: [PATCH 0903/1215] clean up --- src/lib/gadgets/gadgets.ts | 17 ++++++----------- src/lib/int.ts | 15 ++++++++------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 36d5b956c9..d095184388 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -95,10 +95,10 @@ const Gadgets = { * @example * ```ts * const x = Provable.witness(Field, () => Field(12345678n)); - * Gadgets.rangeCheck(32, x); // successfully proves 32-bit range + * Gadgets.rangeCheckN(32, x); // successfully proves 32-bit range * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * Gadgets.rangeCheck(32, xLarge); // throws an error since input exceeds 32 bits + * Gadgets.rangeCheckN(32, xLarge); // throws an error since input exceeds 32 bits * ``` */ rangeCheckN(n: number, x: Field, message?: string) { @@ -109,7 +109,7 @@ const Gadgets = { * Checks whether the input value is in the range [0, 2^n). `n` must be a multiple of 16. * * This function proves that the provided field element can be represented with `n` bits. - * If the field element exceeds `n` bits, an error is thrown. + * If the field element exceeds `n` bits, `Bool(false)` is returned and `Bool(true)` otherwise. * * @param x - The value to be range-checked. * @param n - The number of bits to be considered for the range check. @@ -319,7 +319,7 @@ const Gadgets = { * y.assertEquals(0b110000); // 48 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * leftShift(xLarge, 32); // throws an error since input exceeds 64 bits + * leftShift64(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ leftShift64(field: Field, bits: number) { @@ -334,23 +334,18 @@ const Gadgets = { * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. * - * **Important:** The gadgets assumes that its input is at most 64 bits in size. + * **Important:** The gadgets assumes that its input is at most 32 bits in size. * - * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. + * The output is range checked to 32 bits. * * @param field {@link Field} element to shift. * @param bits Amount of bits to shift the {@link Field} element to the left. The amount should be between 0 and 32 (or else the shift will fail). * - * @throws Throws an error if the input value exceeds 32 bits. - * * @example * ```ts * const x = Provable.witness(Field, () => Field(0b001100)); // 12 in binary * const y = Gadgets.leftShift32(x, 2); // left shift by 2 bits * y.assertEquals(0b110000); // 48 in binary - * - * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * leftShift(xLarge, 32); // throws an error since input exceeds 32 bits * ``` */ leftShift32(field: Field, bits: number) { diff --git a/src/lib/int.ts b/src/lib/int.ts index 824a282fc4..46e3a47dde 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -4,7 +4,6 @@ import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; import { Gadgets } from './gadgets/gadgets.js'; -import { FILE } from 'dns'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -804,10 +803,12 @@ class UInt32 extends CircuitValue { * This operation is similar to the `<<` shift operation in JavaScript, * where bits are shifted to the left, and the overflowing bits are discarded. * - * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, - * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. + * + * The operation expects the input to be range checked to 32 bit. * - * @param bits Amount of bits to shift the {@link UInt32} element to the left. The amount should be between 0 and 64 (or else the shift will fail). + * @param bits Amount of bits to shift the {@link UInt32} element to the left. The amount should be between 0 and 32 (or else the shift will fail). * * @example * ```ts @@ -825,10 +826,10 @@ class UInt32 extends CircuitValue { * This operation is similar to the `>>` shift operation in JavaScript, * where bits are shifted to the right, and the overflowing bits are discarded. * - * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, - * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. + * It’s important to note that these operations are performed considering the big-endian 32-bit representation of the number, + * where the most significant (32th) bit is on the left end and the least significant bit is on the right end. * - * @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 64 (or else the shift will fail). + * @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 32 (or else the shift will fail). * * @example * ```ts From b3d74df4385a7d1061e8c78549149aa4f96d7caa Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 29 Nov 2023 19:29:47 +0100 Subject: [PATCH 0904/1215] doc comment fix --- src/lib/int.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index 46e3a47dde..6fe765e7db 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -831,6 +831,8 @@ class UInt32 extends CircuitValue { * * @param bits Amount of bits to shift the {@link UInt32} element to the right. The amount should be between 0 and 32 (or else the shift will fail). * + * The operation expects the input to be range checked to 32 bit. + * * @example * ```ts * const x = UInt32.from(0b001100); // 12 in binary From 857bf482cf4f1414b34b6be07f888d251bf9335c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 29 Nov 2023 10:56:23 -0800 Subject: [PATCH 0905/1215] feat(.github/workflows): add automated version bump workflow This commit introduces a new GitHub Actions workflow that automatically bumps the project's patch version bi-weekly on Fridays. The workflow also creates a new branch and a PR to `main` for the new version. This automation will help in maintaining a regular release cycle and reduce manual effort. --- .github/workflows/release.yml | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..57ac247805 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,66 @@ +# Purpose: +# Automatically bumps the project's patch version bi-weekly on Fridays. +# +# Details: +# - Triggered at 00:00 UTC every Friday; runs on even weeks of the year. +# - Sets up the environment by checking out the repo and setting up Node.js. +# - Bumps patch version using `npm version patch`, then creates a new branch 'release/x.x.x'. +# - Pushes changes and creates a PR to `main` using GitHub CLI. +# - Can also be triggered manually via `workflow_dispatch`. +name: Version Bump + +on: + workflow_dispatch: # Allow to manually trigger the workflow + schedule: + - cron: "0 0 * * 5" # At 00:00 UTC every Friday + +jobs: + version-bump: + runs-on: ubuntu-latest + + steps: + # Since cronjob syntax doesn't support bi-weekly schedule, we need to check if it's an even week or not + - name: Check if it's an even week + id: check-week + run: | + WEEK_NUM=$(date +'%V') + if [ $((WEEK_NUM % 2)) -eq 0 ]; then + echo "RUN_JOB=true" >> $GITHUB_ENV + else + echo "RUN_JOB=false" >> $GITHUB_ENV + fi + + - name: Check out the repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Configure Git + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Bump patch version + id: version-bump + if: ${{ env.RUN_JOB }} == 'true' + run: | + git fetch --prune --unshallow + NEW_VERSION=$(npm version patch) + echo "New version: $NEW_VERSION" + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + - name: Install npm dependencies + run: npm install + + - name: Create new release branch + run: | + NEW_BRANCH="release/${NEW_VERSION}" + git checkout -b $NEW_BRANCH + git push -u origin $NEW_BRANCH + git push --tags + gh pr create --base main --head $NEW_BRANCH --title "Release $NEW_VERSION" --body "This is an automated PR to update to version $NEW_VERSION" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} From a6da911ca8fe8b44fe912cba96ed0f72345f88fd Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 Nov 2023 20:54:49 +0100 Subject: [PATCH 0906/1215] unsafe constructors to override type strictness --- src/lib/foreign-field.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index ddec83e6ea..36a770090d 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -160,7 +160,7 @@ class ForeignField { Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, { skipMrc: true, }); - return new this.Constructor.AlmostReduced(this.value); + return this.Constructor.AlmostReduced.unsafeFrom(this); } /** @@ -177,7 +177,7 @@ class ForeignField { this.modulus, { skipMrc: true } ); - return Tuple.map(xs, (x) => new this.AlmostReduced(x.value)); + return Tuple.map(xs, this.AlmostReduced.unsafeFrom); } /** @@ -188,7 +188,7 @@ class ForeignField { */ assertCanonicalFieldElement() { this.assertLessThan(this.modulus); - return new this.Constructor.Canonical(this.value); + return this.Constructor.Canonical.unsafeFrom(this); } // arithmetic with full constraints, for safe use @@ -486,6 +486,15 @@ class AlmostForeignField extends ForeignFieldWithMul { Gadgets.multiRangeCheck(x.value); x.assertAlmostReduced(); } + + /** + * Coerce the input to an {@link AlmostForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } } class CanonicalForeignField extends ForeignFieldWithMul { @@ -505,6 +514,15 @@ class CanonicalForeignField extends ForeignFieldWithMul { Gadgets.multiRangeCheck(x.value); x.assertCanonicalFieldElement(); } + + /** + * Coerce the input to a {@link CanonicalForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } } function toLimbs( @@ -605,6 +623,7 @@ function createForeignField(modulus: bigint): typeof ForeignField { static from = ForeignField.from.bind(AlmostField); static sum = ForeignField.sum.bind(AlmostField); static fromBits = ForeignField.fromBits.bind(AlmostField); + static unsafeFrom = AlmostForeignField.unsafeFrom.bind(AlmostField); } class CanonicalField extends CanonicalForeignField { @@ -615,6 +634,7 @@ function createForeignField(modulus: bigint): typeof ForeignField { static from = ForeignField.from.bind(CanonicalField); static sum = ForeignField.sum.bind(CanonicalField); static fromBits = ForeignField.fromBits.bind(CanonicalField); + static unsafeFrom = CanonicalForeignField.unsafeFrom.bind(CanonicalField); } let variants = { From 1d9c5dde3c58f3b55fe444bd47e25ba4ae1dd548 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 29 Nov 2023 13:31:48 -0800 Subject: [PATCH 0907/1215] refactor(release.yml): remove unused id fields from GitHub Actions workflow steps The id fields 'check-week' and 'version-bump' were not being used in the workflow, hence they were removed to clean up the code. --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57ac247805..7f743c0f04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,6 @@ jobs: steps: # Since cronjob syntax doesn't support bi-weekly schedule, we need to check if it's an even week or not - name: Check if it's an even week - id: check-week run: | WEEK_NUM=$(date +'%V') if [ $((WEEK_NUM % 2)) -eq 0 ]; then @@ -44,7 +43,6 @@ jobs: git config --local user.name "GitHub Action" - name: Bump patch version - id: version-bump if: ${{ env.RUN_JOB }} == 'true' run: | git fetch --prune --unshallow From 6ff4f23de36dbba4f0b990d844fb33b44ed59acc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 09:38:04 +0100 Subject: [PATCH 0908/1215] testing helpers --- src/lib/testing/equivalent.ts | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index ab1241b94b..440be13df4 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -17,6 +17,7 @@ export { id, }; export { + spec, field, fieldWithRng, bigintField, @@ -26,6 +27,8 @@ export { array, record, fromRandom, + first, + second, }; export { Spec, ToSpec, FromSpec, SpecFromFunctions, ProvableSpec }; @@ -221,6 +224,41 @@ function equivalentProvable< }; } +// creating specs + +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable: Provable; +}): ProvableSpec; +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + there?: (x: T) => S; + back?: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable?: Provable; +}): Spec { + return { + rng: spec.rng, + there: spec.there ?? (id as any), + back: spec.back ?? (id as any), + assertEqual: spec.assertEqual, + provable: spec.provable, + }; +} + // some useful specs let unit: ToSpec = { back: id, assertEqual() {} }; @@ -301,6 +339,18 @@ function fromRandom(rng: Random): Spec { return { rng, there: id, back: id }; } +function first(spec: Spec): Spec { + return { rng: spec.rng, there: id, back: id }; +} +function second(spec: Spec): Spec { + return { + rng: Random.map(spec.rng, spec.there), + there: id, + back: id, + provable: spec.provable, + }; +} + // helper to ensure two functions throw equivalent errors function handleErrors( From 83b59a6ecc54e83617215a618caa2bc4908e7624 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 09:38:29 +0100 Subject: [PATCH 0909/1215] some docs fixes --- src/lib/foreign-field.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 36a770090d..b5a6c4c181 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -387,7 +387,7 @@ class ForeignField { let l1 = Field.fromBits(bits.slice(1 * limbSize, 2 * limbSize)); let l2 = Field.fromBits(bits.slice(2 * limbSize, 3 * limbSize)); // note: due to the check on the number of bits, we know we return an "almost valid" field element - return new this([l0, l1, l2]) as AlmostForeignField; + return new this.AlmostReduced([l0, l1, l2]); } /** @@ -546,12 +546,11 @@ function isConstant(x: bigint | number | string | ForeignField) { /** * Create a class representing a prime order finite field, which is different from the native {@link Field}. * - * @example * ```ts * const SmallField = createForeignField(17n); // the finite field F_17 * ``` * - * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. + * `createForeignField(p)` takes {@link AlmostForeignField} the prime modulus `p` of the finite field as input, as a bigint. * We support prime moduli up to a size of 259 bits. * * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), @@ -573,23 +572,21 @@ function isConstant(x: bigint | number | string | ForeignField) { * This function returns the `Unreduced` class, which will cause the minimum amount of range checks to be created by default. * If you want to do multiplication, you have two options: * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. - * @example * ```ts * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => ForeignField.from(5)); * ``` * - create your field elements normally and convert them using `x.assertAlmostReduced()`. - * @example * ```ts * let xChecked = x.assertAlmostReduced(); // asserts x < 2^ceil(log2(p)); returns `AlmostForeignField` * ``` * - * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced / "canonical" field elements. + * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. * To convert to a canonical field element, use {@link assertCanonicalFieldElement}: * * ```ts * x.assertCanonicalFieldElement(); // asserts x < p; returns `CanonicalForeignField` * ``` - * You will likely not need `CanonicalForeignField`, except possibly for creating your own interfaces which expect fully reduced field elements. + * You will likely not need canonical fields most of the time. * * Base types for all of these classes are separately exported as {@link UnreducedForeignField}, {@link AlmostForeignField} and {@link CanonicalForeignField}., * From 609733e444998b01304078a6051f4805f5c91ae2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 09:38:39 +0100 Subject: [PATCH 0910/1215] less than test --- src/lib/foreign-field.unit-test.ts | 26 +++++++++++++++++--------- src/lib/gadgets/foreign-field.ts | 2 ++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 9ef6617d6e..b73f03cabd 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -9,9 +9,11 @@ import { import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { - ProvableSpec, bool, equivalentProvable as equivalent, + equivalent as equivalentNonProvable, + first, + spec, throwError, unit, } from './testing/equivalent.js'; @@ -60,24 +62,24 @@ test(Random.scalar, (x0, assert) => { // test equivalence of in-SNARK and out-of-SNARK operations -let f: ProvableSpec = { +let f = spec({ rng: Random.scalar, there: ForeignScalar.from, back: (x) => x.toBigInt(), provable: ForeignScalar.AlmostReduced.provable, -}; -let big264: ProvableSpec = { +}); +let u264 = spec({ rng: Random.bignat(1n << 264n), there: ForeignScalar.from, back: (x) => x.toBigInt(), provable: ForeignScalar.Unreduced.provable, -}; +}); // arithmetic -equivalent({ from: [f, f], to: big264 })(Fq.add, (x, y) => x.add(y)); -equivalent({ from: [f, f], to: big264 })(Fq.sub, (x, y) => x.sub(y)); -equivalent({ from: [f], to: big264 })(Fq.negate, (x) => x.neg()); -equivalent({ from: [f, f], to: big264 })(Fq.mul, (x, y) => x.mul(y)); +equivalent({ from: [f, f], to: u264 })(Fq.add, (x, y) => x.add(y)); +equivalent({ from: [f, f], to: u264 })(Fq.sub, (x, y) => x.sub(y)); +equivalent({ from: [f], to: u264 })(Fq.negate, (x) => x.neg()); +equivalent({ from: [f, f], to: u264 })(Fq.mul, (x, y) => x.mul(y)); equivalent({ from: [f], to: f })( (x) => Fq.inverse(x) ?? throwError('division by 0'), (x) => x.inv() @@ -96,6 +98,12 @@ 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: [u264, first(u264)], to: unit })( + (x, y) => x < y || throwError('not less than'), + (x, y) => x.assertLessThan(y) +); // toBits / fromBits equivalent({ from: [f], to: f })( diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 1290d1f264..9ee2822e04 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -45,6 +45,8 @@ const ForeignField = { assertAlmostFieldElements, 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'); From 2cca56b2a87222373ce8c1fab7c5ec02f7f6c326 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 10:45:57 +0100 Subject: [PATCH 0911/1215] export ff type variants, fix assert equals return --- src/index.ts | 7 ++++++- src/lib/foreign-field.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 78d3707f12..71831d7588 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,12 @@ export type { ProvablePure } from './snarky.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; -export { createForeignField, ForeignField } from './lib/foreign-field.js'; +export { + createForeignField, + ForeignField, + AlmostForeignField, + CanonicalForeignField, +} from './lib/foreign-field.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export type { diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index b5a6c4c181..55cd105160 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -293,14 +293,16 @@ class ForeignField { if (x !== y0) { throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); } - return new this.Constructor.AlmostReduced(this.value); + return new this.Constructor.Canonical(this.value); } Provable.assertEqual( this.Constructor.provable, this, new this.Constructor(y) ); - if (isConstant(y) || y instanceof ForeignFieldWithMul) { + if (isConstant(y) || y instanceof this.Constructor.Canonical) { + return new this.Constructor.Canonical(this.value); + } else if (y instanceof this.Constructor.AlmostReduced) { return new this.Constructor.AlmostReduced(this.value); } else { return this; From 4107423453330b097aaeef73f229edc3c393fc92 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 22 Nov 2023 11:31:04 +0100 Subject: [PATCH 0912/1215] allow passing .provable to methods --- src/lib/proof_system.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 312521374b..08adef5f68 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -513,6 +513,9 @@ function sortMethodArguments( } else if (isAsFields(privateInput)) { allArgs.push({ type: 'witness', index: witnessArgs.length }); witnessArgs.push(privateInput); + } else if (isAsFields((privateInput as any)?.provable)) { + allArgs.push({ type: 'witness', index: witnessArgs.length }); + witnessArgs.push((privateInput as any).provable); } else if (isGeneric(privateInput)) { allArgs.push({ type: 'generic', index: genericArgs.length }); genericArgs.push(privateInput); From 09ecda95aeadbc9cb8afe3f20431face33098dd2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 10:55:46 +0100 Subject: [PATCH 0913/1215] detailed example --- src/examples/crypto/foreign-field.ts | 111 ++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts index d3a910e347..bffdae7654 100644 --- a/src/examples/crypto/foreign-field.ts +++ b/src/examples/crypto/foreign-field.ts @@ -1,9 +1,116 @@ -import { createForeignField } from 'o1js'; +/** + * This example explores the ForeignField API! + * + * We shed light on the subtleties of different variants of foreign field: + * Unreduced, AlmostReduced, and Canonical. + */ +import assert from 'assert'; +import { + createForeignField, + AlmostForeignField, + CanonicalForeignField, + Scalar, + SmartContract, + method, + Provable, + state, + State, +} from 'o1js'; -// toy example - F_17 +// Let's create a small finite field: F_17 class SmallField extends createForeignField(17n) {} let x = SmallField.from(16); x.assertEquals(-1); // 16 = -1 (mod 17) x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// most arithmetic operations return "unreduced" fields, i.e., fields that could be larger than the modulus: + +let z = x.add(x); +assert(z instanceof SmallField.Unreduced); + +// note: "unreduced" doesn't usually mean that the underlying witness is larger than the modulus. +// it just means we haven't _proved_ so.. which means a malicious prover _could_ have managed to make it larger. + +// unreduced fields can be added and subtracted, but not be used in multiplcation: + +z.add(1).sub(x).assertEquals(0); // works + +assert((z as any).mul === undefined); // z.mul() is not defined +assert((z as any).inv === undefined); +assert((z as any).div === undefined); + +// to do multiplication, you need "almost reduced" fields: + +let y: AlmostForeignField = z.assertAlmostReduced(); // adds constraints to prove that z is, in fact, reduced +assert(y instanceof SmallField.AlmostReduced); + +y.mul(y).assertEquals(4); // y.mul() is defined +assert(y.mul(y) instanceof SmallField.Unreduced); // but y.mul() returns an unreduced field again + +y.inv().mul(y).assertEquals(1); // y.inv() is defined (and returns an AlmostReduced field!) + +// to do many multiplications, it's more efficient to reduce fields in batches of 3 elements: +// (in fact, asserting that 3 elements are reduced is almost as cheap as asserting that 1 element is reduced) + +let z1 = y.mul(7); +let z2 = y.add(11); +let z3 = y.sub(13); + +let [z1r, z2r, z3r] = SmallField.assertAlmostReduced(z1, z2, z3); + +z1r.mul(z2r); +z2r.div(z3r); + +// here we get to the reason _why_ we have different variants of foreign fields: +// always proving that they are reduced after every operation would be super inefficient! + +// fields created from constants are already reduced -- in fact, they are _fully reduced_ or "canonical": + +let constant: CanonicalForeignField = SmallField.from(1); +assert(constant instanceof SmallField.Canonical); + +SmallField.from(10000n) satisfies CanonicalForeignField; // works because `from()` takes the input mod p +SmallField.from(-1) satisfies CanonicalForeignField; // works because `from()` takes the input mod p + +// canonical fields are a special case of almost reduced fields at the type level: +constant satisfies AlmostForeignField; +constant.mul(constant); + +// the cheapest way to prove that an existing field element is canonical is to show that it is equal to a constant: + +let u = z.add(x); +let uCanonical = u.assertEquals(-3); +assert(uCanonical instanceof SmallField.Canonical); + +// to use the different variants of foreign fields as smart contract inputs, you might want to create a class for them: +class AlmostSmallField extends SmallField.AlmostReduced {} + +class MyContract extends SmartContract { + @state(AlmostSmallField.provable) x = State(); + + @method myMethod(y: AlmostSmallField) { + let x = y.mul(2); + Provable.log(x); + this.x.set(x.assertAlmostReduced()); + } +} +MyContract.analyzeMethods(); // works + +// btw - we support any finite field up to 259 bits. for example, the seqp256k1 base field: +let Fseqp256k1 = createForeignField((1n << 256n) - (1n << 32n) - 0b1111010001n); + +// or the Pallas scalar field, to do arithmetic on scalars: +let Fq = createForeignField(Scalar.ORDER); + +// also, you can use a number that's not a prime. +// for example, you might want to create a UInt256 type: +let UInt256 = createForeignField(1n << 256n); + +// and now you can do arithmetic modulo 2^256! +let a = UInt256.from(1n << 255n); +let b = UInt256.from((1n << 255n) + 7n); +a.add(b).assertEquals(7); + +// have fun proving finite field algorithms! From cb0a91faff1d7eaa0512f79cfb340aa3ee2ec0b3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 10:58:20 +0100 Subject: [PATCH 0914/1215] crypto examples readme --- src/examples/crypto/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/examples/crypto/README.md diff --git a/src/examples/crypto/README.md b/src/examples/crypto/README.md new file mode 100644 index 0000000000..60d0d50d51 --- /dev/null +++ b/src/examples/crypto/README.md @@ -0,0 +1,5 @@ +# Crypto examples + +These examples show how to use some of the crypto primitives that are supported in provable o1js code. + +- Non-native field arithmetic: `foreign-field.ts` From 4330a98366af4ff289bd26b94b916593efd60c35 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 11:23:08 +0100 Subject: [PATCH 0915/1215] fixup: foreign fields always gets reduced on creation --- src/lib/foreign-field.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index b73f03cabd..f1d0aef3a0 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -100,7 +100,7 @@ equivalent({ from: [f, f], to: unit })( ); // doesn't fail in provable mode just because the range check is not checked by runAndCheck // TODO check all gates -equivalentNonProvable({ from: [u264, first(u264)], to: unit })( +equivalentNonProvable({ from: [f, first(u264)], to: unit })( (x, y) => x < y || throwError('not less than'), (x, y) => x.assertLessThan(y) ); From bdc75e2ef3f2f16bd9781d3fdbf090df874d3ca3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 14:38:27 +0100 Subject: [PATCH 0916/1215] remove unused imports --- src/lib/foreign-field.unit-test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index f1d0aef3a0..5686b3ef49 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,11 +1,6 @@ import { ProvablePure } from '../snarky.js'; import { Field, Group } from './core.js'; -import { - AlmostForeignField, - ForeignField, - UnreducedForeignField, - createForeignField, -} from './foreign-field.js'; +import { ForeignField, createForeignField } from './foreign-field.js'; import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; import { expect } from 'expect'; import { From e7ad795ea97931fa65b07e634633dd61c8954b9e Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 14:51:09 +0100 Subject: [PATCH 0917/1215] move todo out of doccomment --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index bcfc0a41b2..9384aaa767 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -554,6 +554,7 @@ const Gadgets = { * ECDSA verification gadget and helper methods. */ Ecdsa: { + // TODO add an easy way to prove that the public key lies on the curve, and show in the example /** * Verify an ECDSA signature. * @@ -566,7 +567,6 @@ const Gadgets = { * [signature.r, signature.s, msgHash], * Curve.order * ); - * // TODO add an easy way to prove that the public key lies on the curve * * // verify signature * Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); From 660f1a4eceb32051f0a82cff70efe6c7fb029f1c Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 15:09:37 +0100 Subject: [PATCH 0918/1215] return a bool from verify() --- src/bindings | 2 +- src/lib/gadgets/ecdsa.unit-test.ts | 31 ++++++++++++++++++------------ src/lib/gadgets/elliptic-curve.ts | 8 ++++---- src/lib/gadgets/gadgets.ts | 6 +++++- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/bindings b/src/bindings index b432ffd750..80cf218dca 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b432ffd750b4ee961e4b9924f69b5e07bc5368a8 +Subproject commit 80cf218dca605cc91fbde65bff79f2cce9284a47 diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 144578c2cb..9cbcbe4e7d 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -13,12 +13,13 @@ import { assert } from './common.js'; import { foreignField, throwError, uniformForeignField } from './test-utils.js'; import { Second, + bool, equivalentProvable, map, oneOf, record, - unit, } from '../testing/equivalent.js'; +import { Bool } from '../bool.js'; // quick tests const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); @@ -51,27 +52,27 @@ for (let Curve of curves) { // provable method we want to test const verify = (s: Second) => { - Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); + return Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); }; // positive test - equivalentProvable({ from: [signature], to: unit })( - () => {}, + equivalentProvable({ from: [signature], to: bool })( + () => true, verify, 'valid signature verifies' ); // negative test - equivalentProvable({ from: [pseudoSignature], to: unit })( - () => throwError('invalid signature'), + equivalentProvable({ from: [pseudoSignature], to: bool })( + () => false, verify, 'invalid signature fails' ); // test against constant implementation, with both invalid and valid signatures - equivalentProvable({ from: [oneOf(signature, pseudoSignature)], to: unit })( + equivalentProvable({ from: [oneOf(signature, pseudoSignature)], to: bool })( ({ signature, publicKey, msg }) => { - assert(verifyEcdsaConstant(Curve, signature, msg, publicKey), 'verifies'); + return verifyEcdsaConstant(Curve, signature, msg, publicKey); }, verify, 'verify' @@ -99,6 +100,7 @@ const config = { G: { windowSize: 4 }, P: { windowSize: 3 }, ia }; let program = ZkProgram({ name: 'ecdsa', + publicOutput: Bool, methods: { scale: { privateInputs: [], @@ -111,9 +113,7 @@ let program = ZkProgram({ [G, P], [config.G, config.P] ); - Provable.asProver(() => { - console.log(Point.toBigint(R)); - }); + return new Bool(true); }, }, ecdsa: { @@ -126,7 +126,13 @@ let program = ZkProgram({ let msgHash_ = Provable.witness(Field3.provable, () => msgHash); let publicKey_ = Provable.witness(Point.provable, () => publicKey); - Ecdsa.verify(Secp256k1, signature_, msgHash_, publicKey_, config); + return Ecdsa.verify( + Secp256k1, + signature_, + msgHash_, + publicKey_, + config + ); }, }, }, @@ -163,3 +169,4 @@ let proof = await program.ecdsa(); console.timeEnd('ecdsa verify (prove)'); assert(await program.verify(proof), 'proof verifies'); +proof.publicOutput.assertTrue('signature verifies'); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index da8bbe924d..203921bc9e 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -163,8 +163,7 @@ function verifyEcdsa( Field3.toBigint(msgHash), Point.toBigint(publicKey) ); - assert(isValid, 'invalid signature'); - return; + return new Bool(isValid); } // provable case @@ -191,7 +190,7 @@ function verifyEcdsa( // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: // it's the callers responsibility to check that the signature is valid/unique in whatever way it makes sense for the application let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); - Provable.assertEqual(Field3.provable, Rx, r); + return Provable.equal(Field3.provable, Rx, r); } /** @@ -295,7 +294,8 @@ function verifyEcdsaConstant( msgHash: bigint, publicKey: point ) { - let pk = Curve.fromNonzero(publicKey); + let pk = Curve.from(publicKey); + if (Curve.equal(pk, Curve.zero)) return false; if (!Curve.isOnCurve(pk)) return false; if (Curve.hasCofactor && !Curve.isInSubgroup(pk)) return false; if (r < 1n || r >= Curve.order) return false; diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 9384aaa767..6f9f24b9a1 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -558,6 +558,9 @@ const Gadgets = { /** * Verify an ECDSA signature. * + * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. + * So, to actually prove validity of a signature, you need to assert that the result is true. + * * @example * ```ts * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); @@ -569,7 +572,8 @@ const Gadgets = { * ); * * // verify signature - * Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); + * let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); + * isValid.assertTrue(); * ``` */ verify( From 1a55a5d3f7613684db02d42ccd0f3eb650d58c9c Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 15:49:16 +0100 Subject: [PATCH 0919/1215] update vk --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 9c4f4f259a..e2899eff8e 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "1025b5f3a56c5366fd44d13f2678bba563c9581c6bacfde2b82a8dd49e33f2a2", + "digest": "1ab8f6c699e144aeaa39f88c8392a5a5053dcf58249a7ff561ac3fe343b15ede", "methods": { "verifyEcdsa": { - "rows": 38823, - "digest": "65c6f9efe1069f73adf3a842398f95d2" + "rows": 38830, + "digest": "513f0fcca515c10ba593720f7d62aedb" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAPuFca4nCkavRK/kopNaYXLXQp30LgUt/V0UJqSuP4QXztIlWzZFWZ0ETZmroQESjM3Cl1C6O17KMs0QEJFJmQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgW9fGIdxPWc2TMCE7mIgqn1pcbKC1rjoMpStE7yiXTC4URGk/USFy0xf0tpXILTP7+nqebXCb+AvUnHe6cManJ146ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "2818165315298159349200200610054154136732885996642834698461943280515655745528" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAAG34cM0Ar7ZU03sX2S9PKS4No4BMUcksGjbfn3aHbcene6J0DpK63As94tUGYA96xWEsWEAzdNVKR5Q2EmGgAAgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgQDEEGBl7z9wR1M+j5SyngGYnHqFGwnchqlg72T+1+ySvANQ7vM6jdpYI6hmX7+QnKXHKYKAQGMiwk83Sf/b5HF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "24873772351090152857267160919303237684044478603326712805175843693860023193162" } } } \ No newline at end of file From 57576fae8e828a778213abb9e23275b5ccd6ab71 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 16:15:37 +0100 Subject: [PATCH 0920/1215] wip adapting ecdsa soundness to bool return --- src/lib/gadgets/elliptic-curve.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 203921bc9e..888dfaab4d 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -187,9 +187,11 @@ function verifyEcdsa( // this ^ already proves that R != 0 (part of ECDSA verification) // reduce R.x modulo the curve order - // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: - // it's the callers responsibility to check that the signature is valid/unique in whatever way it makes sense for the application let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); + // we have to check that the result Rx is canonical, we then check if it _exactly_ equal to the input r. + // if we would allow non-canonical Rx, a prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. + // TODO + return Provable.equal(Field3.provable, Rx, r); } From 5f5cd316a8418349b4d3195d028364a724158781 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 16:22:19 +0100 Subject: [PATCH 0921/1215] cherry pick assertLessThan --- src/lib/gadgets/foreign-field.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index d45a800331..4b92c54299 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -42,6 +42,9 @@ const ForeignField = { sub(x: Field3, y: Field3, f: bigint) { return sum([x, y], [-1n], f); }, + negate(x: Field3, f: bigint) { + return sum([Field3.from(0n), x], [-1n], f); + }, sum, Sum(x: Field3) { return new Sum(x); @@ -53,6 +56,21 @@ const ForeignField = { assertMul, assertAlmostFieldElements, + + 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 or 1) + ForeignField.negate(x, f - 1n); + }, }; /** From af6de47cb7203344de9954a2c0289eac6c6fcfb6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 16:28:35 +0100 Subject: [PATCH 0922/1215] finish adapting ecdsa final step --- src/lib/gadgets/elliptic-curve.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 888dfaab4d..ea64483b64 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -188,9 +188,10 @@ function verifyEcdsa( // reduce R.x modulo the curve order let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); - // we have to check that the result Rx is canonical, we then check if it _exactly_ equal to the input r. - // if we would allow non-canonical Rx, a prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. - // TODO + + // we have to prove that Rx is canonical, because we check signature validity based on whether Rx _exactly_ equals the input r. + // if we allowed non-canonical Rx, the prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. + ForeignField.assertLessThan(Rx, Curve.order); return Provable.equal(Field3.provable, Rx, r); } From c193bd7e2fd0ed17739c1e05243698f105300ba4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 16:29:57 +0100 Subject: [PATCH 0923/1215] update vk --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index e2899eff8e..6044f0bcf3 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "1ab8f6c699e144aeaa39f88c8392a5a5053dcf58249a7ff561ac3fe343b15ede", + "digest": "339463a449bf05b95a8c33f61cfe8ce434517139ca76d46acd8156fa148fa17d", "methods": { "verifyEcdsa": { - "rows": 38830, - "digest": "513f0fcca515c10ba593720f7d62aedb" + "rows": 38836, + "digest": "1d54d1addf7630e3eb226959de232cc3" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAAG34cM0Ar7ZU03sX2S9PKS4No4BMUcksGjbfn3aHbcene6J0DpK63As94tUGYA96xWEsWEAzdNVKR5Q2EmGgAAgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgQDEEGBl7z9wR1M+j5SyngGYnHqFGwnchqlg72T+1+ySvANQ7vM6jdpYI6hmX7+QnKXHKYKAQGMiwk83Sf/b5HF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "24873772351090152857267160919303237684044478603326712805175843693860023193162" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAGIC/c5mlvJEtw3GmFSOhllzNMb5rze5XPBrZhVM6zUgjHCxrGo9kh7sUu/CJtpp5k56fIqgmrAEIdVJ3IloJAUgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgtRrSDSqUoCxu0FXG8VDNFSk+wPBfgTjvTmC9gbTjPgfr95gk0HAAeyDFGWwKsDvU4bkqsacYCSWVZ7OFcx2LDV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "27986645693165294918748566661718640959750969878894001222795403177870132383423" } } } \ No newline at end of file From 66a9dc138689c83f7a49f7b0f6f027606a807f7d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 17:10:54 +0100 Subject: [PATCH 0924/1215] document `config` --- src/lib/gadgets/elliptic-curve.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index ea64483b64..8d45b6e428 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -140,6 +140,22 @@ function double(p1: Point, f: bigint) { return { x: x3, y: y3 }; } +/** + * Verify an ECDSA signature. + * + * Details about the `config` parameter: + * - For both the generator point `G` and public key `P`, `config` allows you to specify: + * - the `windowSize` which is used in scalar multiplication for this point. + * flexibility is good because the optimal window size is different for constant and non-constant points. + * empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. + * our defaults reflect that the generator is always constant and the public key is variable in typical applications. + * - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. + * if these are not provided, they are computed on the fly. + * for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. + * for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. + * in that case, passing them in would avoid computing them in-circuit and save a few constraints. + * - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. + */ function verifyEcdsa( Curve: CurveAffine, signature: Ecdsa.Signature, From 9f77a55ca0763a2e83c122f010c4fe9fb8972ec9 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 09:25:43 -0800 Subject: [PATCH 0925/1215] chore(release.yml): change automatic version bump schedule from Fridays to Tuesdays to better align with team's workflow --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f743c0f04..d903bcb3ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ # Purpose: -# Automatically bumps the project's patch version bi-weekly on Fridays. +# Automatically bumps the project's patch version bi-weekly on Tuesdays. # # Details: -# - Triggered at 00:00 UTC every Friday; runs on even weeks of the year. +# - Triggered at 00:00 UTC every Tuesday; runs on even weeks of the year. # - Sets up the environment by checking out the repo and setting up Node.js. # - Bumps patch version using `npm version patch`, then creates a new branch 'release/x.x.x'. # - Pushes changes and creates a PR to `main` using GitHub CLI. @@ -12,7 +12,7 @@ name: Version Bump on: workflow_dispatch: # Allow to manually trigger the workflow schedule: - - cron: "0 0 * * 5" # At 00:00 UTC every Friday + - cron: "0 0 * * 2" # At 00:00 UTC every Tuesday jobs: version-bump: From b293d770b234119725d710783ae22914d270f783 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 10:05:05 -0800 Subject: [PATCH 0926/1215] feat: add update-changelog.sh script to automate CHANGELOG.md updates This script identifies the latest version number in the CHANGELOG, increments the patch segment of the version number for the new release, retrieves the current Git commit hash, and updates the CHANGELOG.md file accordingly. This change is done to automate the process of updating the CHANGELOG.md in response to new releases. --- update-changelog.sh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 update-changelog.sh diff --git a/update-changelog.sh b/update-changelog.sh new file mode 100644 index 0000000000..701a1bc491 --- /dev/null +++ b/update-changelog.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# CHANGELOG Update Script for New Releases +# +# This script automates the process of updating the CHANGELOG.md in response to new releases. +# It performs the following actions: +# +# 1. Identifies the latest version number in the CHANGELOG (following semantic versioning). +# 2. Increments the patch segment of the version number for the new release. +# 3. Retrieves the current Git commit hash and truncates it for brevity. +# 4. Updates the CHANGELOG.md file: +# - Adds a new entry for the upcoming release using the incremented version number. +# - Updates the link for the [Unreleased] section to point from the current commit to HEAD. +# +# Usage: +# It should be run in the root directory of the repository where the CHANGELOG.md is located. +# Ensure that you have the necessary permissions to commit and push changes to the repository. + +# Step 1: Capture the latest version +latest_version=$(grep -oP '\[\K[0-9]+\.[0-9]+\.[0-9]+(?=\])' CHANGELOG.md | head -1) +echo "Latest version: $latest_version" + +# Step 2: Bump the patch version +IFS='.' read -r -a version_parts <<< "$latest_version" +let version_parts[2]+=1 +new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" +echo "New version: $new_version" + +# Step 3: Capture the current git commit and truncate it to the first 9 characters +current_commit=$(git rev-parse HEAD | cut -c 1-9) +echo "Current commit: $current_commit" + +# Step 4: Update the CHANGELOG +sed -i "s/\[Unreleased\](.*\.\.\.HEAD)/\[Unreleased\](https:\/\/github.com\/o1-labs\/o1js\/compare\/$current_commit...HEAD)\n\n## \[$new_version\](https:\/\/github.com\/o1-labs\/o1js\/compare\/1ad7333e9e...$current_commit)/" CHANGELOG.md From 9637ac72513de849b014f4f7c72b6a82c3c44a6e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 10:05:28 -0800 Subject: [PATCH 0927/1215] feat(release.yml): add step to update CHANGELOG.md in GitHub workflow --- .github/workflows/release.yml | 4 ++++ package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f743c0f04..1de414a6e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,10 @@ jobs: - name: Install npm dependencies run: npm install + - name: Update CHANGELOG.md + run: | + npm run update-changelog + - name: Create new release branch run: | NEW_BRANCH="release/${NEW_VERSION}" diff --git a/package.json b/package.json index 6d880daea0..b890fd3dba 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2eTestsBuildHelper.js && cp -rf src/examples/plain-html/index.html src/examples/plain-html/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", "e2e:run-server": "node dist/web/server.js", "e2e:install": "npx playwright install --with-deps", - "e2e:show-report": "npx playwright show-report tests/report" + "e2e:show-report": "npx playwright show-report tests/report", + "update-changelog": "./update-changelog.sh" }, "author": "O(1) Labs", "devDependencies": { From 52aa9341ca2e42f981d82b6928d92ded065a33b3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 10:06:01 -0800 Subject: [PATCH 0928/1215] chore(update-changelog.sh): change file permissions to make it executable --- update-changelog.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 update-changelog.sh diff --git a/update-changelog.sh b/update-changelog.sh old mode 100644 new mode 100755 From 26490cf29af74d2184e0355f81bef5fb9709e841 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 10:15:40 -0800 Subject: [PATCH 0929/1215] feat(release.yml): automate CHANGELOG.md update and commit in GitHub workflow This change allows the GitHub workflow to automatically update and commit the CHANGELOG.md file whenever a new version is released. This reduces manual effort and ensures that the CHANGELOG is always up-to-date. --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1de414a6e4..dea1d66c9b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,6 +56,8 @@ jobs: - name: Update CHANGELOG.md run: | npm run update-changelog + git add CHANGELOG.md + git commit -m "Update CHANGELOG for new version $NEW_VERSION" - name: Create new release branch run: | From 5384087a276439f405f0d23ec479f5a34813b670 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:14:03 +0100 Subject: [PATCH 0930/1215] make foreign field struct-friendly --- src/lib/foreign-field.ts | 55 +++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index ee1f04d2c5..242d71cd69 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,4 +1,3 @@ -import { ProvablePure } from '../snarky.js'; import { mod, Fp } from '../bindings/crypto/finite_field.js'; import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; @@ -8,6 +7,7 @@ import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './gadgets/common.js'; import { l3, l } from './gadgets/range-check.js'; +import { ProvablePureExtended } from './circuit_value.js'; // external API export { createForeignField }; @@ -106,6 +106,7 @@ class ForeignField { * Coerce the input to a {@link ForeignField}. */ static from(x: bigint | number | string): CanonicalForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField; static from(x: ForeignField | bigint | number | string): ForeignField { if (x instanceof ForeignField) return x; return new this.Canonical(x); @@ -412,21 +413,6 @@ class ForeignField { assert(this._provable !== undefined, 'ForeignField class not initialized.'); return this._provable; } - - /** - * Convert foreign field element to JSON - */ - static toJSON(x: ForeignField) { - return x.toBigInt().toString(); - } - - /** - * Convert foreign field element from JSON - */ - static fromJSON(x: string) { - // TODO be more strict about allowed values - return new this(x); - } } class ForeignFieldWithMul extends ForeignField { @@ -475,7 +461,9 @@ class ForeignFieldWithMul extends ForeignField { class UnreducedForeignField extends ForeignField { type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; - static _provable: ProvablePure | undefined = undefined; + static _provable: + | ProvablePureExtended + | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); return this._provable; @@ -493,7 +481,9 @@ class AlmostForeignField extends ForeignFieldWithMul { super(x); } - static _provable: ProvablePure | undefined = undefined; + static _provable: + | ProvablePureExtended + | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); return this._provable; @@ -521,7 +511,9 @@ class CanonicalForeignField extends ForeignFieldWithMul { super(x); } - static _provable: ProvablePure | undefined = undefined; + static _provable: + | ProvablePureExtended + | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); return this._provable; @@ -609,7 +601,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * * @param modulus the modulus of the finite field you are instantiating */ -function createForeignField(modulus: bigint): typeof ForeignField { +function createForeignField(modulus: bigint): typeof UnreducedForeignField { assert( modulus > 0n, `ForeignField: modulus must be positive, got ${modulus}` @@ -676,7 +668,7 @@ type Constructor = new (...args: any[]) => T; function provable( Class: Constructor & { check(x: ForeignField): void } -): ProvablePure { +): ProvablePureExtended { return { toFields(x) { return x.value; @@ -694,5 +686,26 @@ function provable( check(x: ForeignField) { Class.check(x); }, + // ugh + toJSON(x: ForeignField) { + return x.toBigInt().toString(); + }, + fromJSON(x: string) { + // TODO be more strict about allowed values + return new Class(x); + }, + empty() { + return new Class(0n); + }, + toInput(x) { + let l_ = Number(l); + return { + packed: [ + [x.value[0], l_], + [x.value[1], l_], + [x.value[2], l_], + ], + }; + }, }; } From b9205c4136c8d0f22bfb82b88769586b05435a5f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:14:11 +0100 Subject: [PATCH 0931/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 80cf218dca..700639bc5d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 80cf218dca605cc91fbde65bff79f2cce9284a47 +Subproject commit 700639bc5df5b7e072446e3196bb0d3594fb32db From c6989dd71095c862cdf3ce7543b865ea753e768f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:25:30 +0100 Subject: [PATCH 0932/1215] fix foreign curve compilation except the class return --- src/lib/foreign-curve.ts | 225 +++++++++------------------------------ 1 file changed, 53 insertions(+), 172 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index bf2733d05d..96a2654417 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,41 +1,23 @@ -import { createCurveAffine } from '../bindings/crypto/elliptic_curve.js'; +import { + CurveParams, + createCurveAffine, +} from '../bindings/crypto/elliptic_curve.js'; import { Snarky } from '../snarky.js'; import { Bool } from './bool.js'; import type { Group } from './group.js'; import { Struct, isConstant } from './circuit_value.js'; -import { - ForeignField, - ForeignFieldConst, - ForeignFieldVar, - createForeignField, -} from './foreign-field.js'; -import { MlBigint } from './ml/base.js'; +import { AlmostForeignField, createForeignField } from './foreign-field.js'; import { MlBoolArray } from './ml/fields.js'; -import { inCheckedComputation } from './provable-context.js'; +import { EllipticCurve, Point } from './gadgets/elliptic-curve.js'; +import { Field3 } from './gadgets/foreign-field.js'; // external API -export { createForeignCurve, CurveParams }; +export { createForeignCurve }; // internal API -export { - ForeignCurveVar, - ForeignCurveConst, - MlCurveParams, - MlCurveParamsWithIa, - ForeignCurveClass, - toMl as affineToMl, -}; +export { ForeignCurveClass }; -type MlAffine = [_: 0, x: F, y: F]; -type ForeignCurveVar = MlAffine; -type ForeignCurveConst = MlAffine; - -type AffineBigint = { x: bigint; y: bigint }; -type Affine = { x: ForeignField; y: ForeignField }; - -function toMl({ x, y }: Affine): ForeignCurveVar { - return [0, x.value, y.value]; -} +type Affine = { x: AlmostForeignField; y: AlmostForeignField }; type ForeignCurveClass = ReturnType; @@ -58,29 +40,27 @@ type ForeignCurveClass = ReturnType; * use the optional `{ unsafe: true }` configuration. See {@link createForeignField} for details. * This option is applied to both the scalar field and the base field. * - * @param curve parameters for the elliptic curve you are instantiating + * @param params parameters for the elliptic curve you are instantiating * @param options * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. */ -function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { - const curveParamsMl = Snarky.foreignCurve.create(MlCurveParams(curve)); - const curveName = curve.name; +function createForeignCurve(params: CurveParams) { + const Curve = createCurveAffine(params); - class BaseField extends createForeignField(curve.modulus, { unsafe }) {} - class ScalarField extends createForeignField(curve.order, { unsafe }) {} + const BaseFieldUnreduced = createForeignField(params.modulus); + const ScalarFieldUnreduced = createForeignField(params.order); + class BaseField extends BaseFieldUnreduced.AlmostReduced {} + class ScalarField extends ScalarFieldUnreduced.AlmostReduced {} // this is necessary to simplify the type of ForeignCurve, to avoid // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. - const Affine: Struct = Struct({ x: BaseField, y: BaseField }); - - const ConstantCurve = createCurveAffine({ - p: curve.modulus, - order: curve.order, - a: curve.a, - b: curve.b, - generator: curve.gen, + const Affine: Struct = Struct({ + x: BaseField.AlmostReduced.provable, + y: BaseField.AlmostReduced.provable, }); + const ConstantCurve = createCurveAffine(params); + function toBigint(g: Affine) { return { x: g.x.toBigInt(), y: g.y.toBigInt() }; } @@ -98,24 +78,13 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { * * **Warning**: This fails for a constant input which does not represent an actual point on the curve. */ - constructor( - g: - | { x: BaseField | bigint | number; y: BaseField | bigint | number } - | ForeignCurveVar - ) { - let x_: BaseField; - let y_: BaseField; - // ForeignCurveVar - if (Array.isArray(g)) { - let [, x, y] = g; - x_ = new BaseField(x); - y_ = new BaseField(y); - } else { - let { x, y } = g; - x_ = BaseField.from(x); - y_ = BaseField.from(y); - } - super({ x: x_, y: y_ }); + constructor(g: { + x: BaseField | Field3 | bigint | number; + y: BaseField | Field3 | bigint | number; + }) { + let x = new BaseField(g.x); + let y = new BaseField(g.y); + super({ x, y }); // don't allow constants that aren't on the curve if (this.isConstant()) this.assertOnCurve(); } @@ -132,28 +101,12 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { return new ForeignCurve(g); } - static #curveParamsMlVar: unknown | undefined; - - /** - * Initialize usage of the curve. This function has to be called once per provable method to use the curve. - */ - static initialize() { - if (!inCheckedComputation()) return; - ForeignCurve.#curveParamsMlVar = - Snarky.foreignCurve.paramsToVars(curveParamsMl); - } - - static _getParams(name: string): unknown { - if (ForeignCurve.#curveParamsMlVar === undefined) { - throw Error( - `${name}(): You must call ${this.name}.initialize() once per provable method to use ${curveName}.` - ); - } - return ForeignCurve.#curveParamsMlVar; + static generator = new ForeignCurve(params.generator); + static modulus = params.modulus; + get modulus() { + return params.modulus; } - static generator = new ForeignCurve(curve.gen); - /** * Checks whether this curve point is constant. * @@ -183,8 +136,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { let z = ConstantCurve.add(toConstant(this), toConstant(h_)); return new ForeignCurve(z); } - let curve = ForeignCurve._getParams(`${this.constructor.name}.add`); - let p = Snarky.foreignCurve.add(toMl(this), toMl(h_), curve); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), this.modulus); return new ForeignCurve(p); } @@ -196,8 +148,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { let z = ConstantCurve.double(toConstant(this)); return new ForeignCurve(z); } - let curve = ForeignCurve._getParams(`${this.constructor.name}.double`); - let p = Snarky.foreignCurve.double(toMl(this), curve); + let p = EllipticCurve.double(toPoint(this), this.modulus); return new ForeignCurve(p); } @@ -209,12 +160,10 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { let z = ConstantCurve.negate(toConstant(this)); return new ForeignCurve(z); } - let curve = ForeignCurve._getParams(`${this.constructor.name}.negate`); - let p = Snarky.foreignCurve.negate(toMl(this), curve); - return new ForeignCurve(p); + throw Error('unimplemented'); } - static #assertOnCurve(g: Affine) { + private static assertOnCurve(g: Affine) { if (isConstant(ForeignCurve, g)) { let isOnCurve = ConstantCurve.isOnCurve(toConstant(g)); if (!isOnCurve) @@ -225,8 +174,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { ); return; } - let curve = ForeignCurve._getParams(`${this.name}.assertOnCurve`); - Snarky.foreignCurve.assertOnCurve(toMl(g), curve); + throw Error('unimplemented'); } /** @@ -234,7 +182,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { * y^2 = x^3 + ax + b */ assertOnCurve() { - ForeignCurve.#assertOnCurve(this); + ForeignCurve.assertOnCurve(this); } // TODO wrap this in a `Scalar` type which is a Bool array under the hood? @@ -242,22 +190,21 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { * Elliptic curve scalar multiplication, where the scalar is represented as a little-endian * array of bits, and each bit is represented by a {@link Bool}. */ - scale(scalar: Bool[]) { - if (this.isConstant() && scalar.every((b) => b.isConstant())) { - let scalar0 = scalar.map((b) => b.toBoolean()); + scale(scalar: ScalarField) { + if (this.isConstant() && scalar.isConstant()) { + let scalar0 = scalar.toBigInt(); let z = ConstantCurve.scale(toConstant(this), scalar0); return new ForeignCurve(z); } - let curve = ForeignCurve._getParams(`${this.constructor.name}.scale`); - let p = Snarky.foreignCurve.scale( - toMl(this), - MlBoolArray.to(scalar), - curve + let p = EllipticCurve.multiScalarMul( + Curve, + [scalar.value], + [toPoint(this)] ); return new ForeignCurve(p); } - static #assertInSubgroup(g: Affine) { + private static assertInSubgroup(g: Affine) { if (isConstant(Affine, g)) { let isInGroup = ConstantCurve.isInSubgroup(toConstant(g)); if (!isInGroup) @@ -268,8 +215,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { ); return; } - let curve_ = ForeignCurve._getParams(`${curveName}.assertInSubgroup`); - Snarky.foreignCurve.checkSubgroup(toMl(g), curve_); + throw Error('unimplemented'); } /** @@ -277,7 +223,7 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { * by performing the scalar multiplication. */ assertInSubgroup() { - ForeignCurve.#assertInSubgroup(this); + ForeignCurve.assertInSubgroup(this); } /** @@ -289,10 +235,9 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { * we don't check that curve elements are valid by default. */ static check(g: Affine) { - if (unsafe) return; super.check(g); // check that x, y are valid field elements - ForeignCurve.#assertOnCurve(g); - if (ConstantCurve.hasCofactor) ForeignCurve.#assertInSubgroup(g); + ForeignCurve.assertOnCurve(g); + if (ConstantCurve.hasCofactor) ForeignCurve.assertInSubgroup(g); } static BaseField = BaseField; @@ -303,70 +248,6 @@ function createForeignCurve(curve: CurveParams, { unsafe = false } = {}) { return ForeignCurve; } -/** - * Parameters defining an elliptic curve in short Weierstraß form - * y^2 = x^3 + ax + b - */ -type CurveParams = { - /** - * Human-friendly name for the curve - */ - name: string; - /** - * Base field modulus - */ - modulus: bigint; - /** - * Scalar field modulus = group order - */ - order: bigint; - /** - * Cofactor = size of EC / order - * - * This can be left undefined if the cofactor is 1. - */ - cofactor?: bigint; - /** - * The `a` parameter in the curve equation y^2 = x^3 + ax + b - */ - a: bigint; - /** - * The `b` parameter in the curve equation y^2 = x^3 + ax + b - */ - b: bigint; - /** - * Generator point - */ - gen: AffineBigint; -}; - -type MlBigintPoint = MlAffine; - -function MlBigintPoint({ x, y }: AffineBigint): MlBigintPoint { - return [0, MlBigint(x), MlBigint(y)]; -} - -type MlCurveParams = [ - _: 0, - modulus: MlBigint, - order: MlBigint, - a: MlBigint, - b: MlBigint, - gen: MlBigintPoint -]; -type MlCurveParamsWithIa = [ - ...params: MlCurveParams, - ia: [_: 0, acc: MlBigintPoint, neg_acc: MlBigintPoint] -]; - -function MlCurveParams(params: CurveParams): MlCurveParams { - let { modulus, order, a, b, gen } = params; - return [ - 0, - MlBigint(modulus), - MlBigint(order), - MlBigint(a), - MlBigint(b), - MlBigintPoint(gen), - ]; +function toPoint({ x, y }: Affine): Point { + return { x: x.value, y: y.value }; } From 321fca09bbe71fc7b9baea3e757950732cae2be3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:30:17 +0100 Subject: [PATCH 0933/1215] fixup foreign field --- src/lib/gadgets/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 376be060d9..a8fdabc301 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -9,7 +9,7 @@ import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Tuple, TupleN, TupleN } from '../util/types.js'; +import { Tuple, TupleN } from '../util/types.js'; import { assertOneOf } from './basic.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; import { From 8459851e83624eb8d3e3a10cef0737d4faa01480 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:30:28 +0100 Subject: [PATCH 0934/1215] delete duplicate curve params --- src/lib/foreign-curve-params.ts | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/lib/foreign-curve-params.ts diff --git a/src/lib/foreign-curve-params.ts b/src/lib/foreign-curve-params.ts deleted file mode 100644 index d2e7234768..0000000000 --- a/src/lib/foreign-curve-params.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Fp, Fq } from '../bindings/crypto/finite_field.js'; -import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; -import type { CurveParams } from './foreign-curve.js'; - -export { secp256k1Params, vestaParams }; - -const secp256k1Params: CurveParams = { - name: 'secp256k1', - modulus: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn, - order: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n, - a: 0n, - b: 7n, - gen: { - x: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n, - y: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n, - }, -}; - -const vestaParams: CurveParams = { - name: 'Vesta', - modulus: Fq.modulus, - order: Fp.modulus, - a: 0n, - b: V.b, - gen: V.one, -}; From 56795cfe4db17997f46a39fd9345b6e8afb86220 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:34:38 +0100 Subject: [PATCH 0935/1215] foreign field: remove duplicate private method --- src/lib/foreign-field.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 242d71cd69..337d4e0799 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -97,11 +97,6 @@ class ForeignField { this.value = Field3.from(mod(BigInt(x), p)); } - private static toLimbs(x: bigint | number | string | ForeignField): Field3 { - if (x instanceof ForeignField) return x.value; - return Field3.from(mod(BigInt(x), this.modulus)); - } - /** * Coerce the input to a {@link ForeignField}. */ @@ -251,7 +246,7 @@ class ForeignField { */ static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { const p = this.modulus; - let fields = xs.map((x) => this.toLimbs(x)); + let fields = xs.map((x) => toLimbs(x, p)); let ops = operations.map((op) => (op === 1 ? 1n : -1n)); let z = Gadgets.ForeignField.sum(fields, ops, p); return new this.Unreduced(z); From bb61f3dad1b8a677420d93a7c5f680b4f933dfb6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:37:30 +0100 Subject: [PATCH 0936/1215] remove private --- src/lib/foreign-curve.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 96a2654417..f5c7151037 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -55,8 +55,8 @@ function createForeignCurve(params: CurveParams) { // this is necessary to simplify the type of ForeignCurve, to avoid // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. const Affine: Struct = Struct({ - x: BaseField.AlmostReduced.provable, - y: BaseField.AlmostReduced.provable, + x: BaseField.provable, + y: BaseField.provable, }); const ConstantCurve = createCurveAffine(params); @@ -163,7 +163,7 @@ function createForeignCurve(params: CurveParams) { throw Error('unimplemented'); } - private static assertOnCurve(g: Affine) { + static assertOnCurve(g: Affine) { if (isConstant(ForeignCurve, g)) { let isOnCurve = ConstantCurve.isOnCurve(toConstant(g)); if (!isOnCurve) @@ -204,7 +204,7 @@ function createForeignCurve(params: CurveParams) { return new ForeignCurve(p); } - private static assertInSubgroup(g: Affine) { + static assertInSubgroup(g: Affine) { if (isConstant(Affine, g)) { let isInGroup = ConstantCurve.isInSubgroup(toConstant(g)); if (!isInGroup) From c0d886785a78dc812b4a3cbb12538912a26ec8a9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:37:37 +0100 Subject: [PATCH 0937/1215] compiles --- src/index.ts | 1 - src/lib/foreign-ecdsa.ts | 72 ++++++++-------------------------------- 2 files changed, 13 insertions(+), 60 deletions(-) diff --git a/src/index.ts b/src/index.ts index 99723d17d6..8d5750ef7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,6 @@ export { } from './lib/foreign-field.js'; export { createForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa } from './lib/foreign-ecdsa.js'; -export { vestaParams, secp256k1Params } from './lib/foreign-curve-params.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export type { diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index f71a688a1e..129f4114b3 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,29 +1,13 @@ -import { inverse, mod } from '../bindings/crypto/finite_field.js'; -import { CurveAffine } from '../bindings/crypto/elliptic_curve.js'; -import { Snarky } from '../snarky.js'; +import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; import { Struct, isConstant } from './circuit_value.js'; -import { - CurveParams, - ForeignCurveClass, - affineToMl, - createForeignCurve, -} from './foreign-curve.js'; -import { ForeignField, ForeignFieldVar } from './foreign-field.js'; +import { ForeignCurveClass, createForeignCurve } from './foreign-curve.js'; +import { AlmostForeignField } from './foreign-field.js'; +import { verifyEcdsaConstant } from './gadgets/elliptic-curve.js'; // external API export { createEcdsa }; -// internal API -export { ForeignSignatureVar }; - -type MlSignature = [_: 0, x: F, y: F]; -type ForeignSignatureVar = MlSignature; - -type Signature = { r: ForeignField; s: ForeignField }; - -function signatureToMl({ r, s }: Signature): ForeignSignatureVar { - return [0, r.value, s.value]; -} +type Signature = { r: AlmostForeignField; s: AlmostForeignField }; /** * Returns a class {@link EcdsaSignature} enabling to parse and verify ECDSA signature in provable code, @@ -31,12 +15,15 @@ function signatureToMl({ r, s }: Signature): ForeignSignatureVar { */ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let Curve0: ForeignCurveClass = - 'gen' in curve ? createForeignCurve(curve) : curve; + 'b' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} class Scalar extends Curve.Scalar {} class BaseField extends Curve.BaseField {} - const Signature: Struct = Struct({ r: Scalar, s: Scalar }); + const Signature: Struct = Struct({ + r: Scalar.provable, + s: Scalar.provable, + }); class EcdsaSignature extends Signature { static Curve = Curve0; @@ -85,7 +72,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let publicKey_ = Curve.from(publicKey); if (isConstant(Signature, this)) { - let isValid = verifyEcdsa( + let isValid = verifyEcdsaConstant( Curve.Bigint, { r: this.r.toBigInt(), s: this.s.toBigInt() }, msgHash_.toBigInt(), @@ -96,11 +83,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { } return; } - let curve = Curve0._getParams(`${this.constructor.name}.verify`); - let signatureMl = signatureToMl(this); - let msgHashMl = msgHash_.value; - let publicKeyMl = affineToMl(publicKey_); - Snarky.ecdsa.verify(signatureMl, msgHashMl, publicKeyMl, curve); + throw Error('unimplemented'); } /** @@ -115,9 +98,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { throw Error(`${this.name}.check(): s must be non-zero`); return; } - let curve = Curve0._getParams(`${this.name}.check`); - let signatureMl = signatureToMl(signature); - Snarky.ecdsa.assertValidSignature(signatureMl, curve); + throw Error('unimplemented'); } static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); @@ -125,30 +106,3 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { return EcdsaSignature; } - -/** - * Bigint implementation of ECDSA verify - */ -function verifyEcdsa( - Curve: CurveAffine, - { r, s }: { r: bigint; s: bigint }, - msgHash: bigint, - publicKey: { x: bigint; y: bigint } -) { - let q = Curve.order; - let QA = Curve.fromNonzero(publicKey); - if (!Curve.isOnCurve(QA)) return false; - if (Curve.hasCofactor && !Curve.isInSubgroup(QA)) return false; - if (r < 1n || r >= Curve.order) return false; - if (s < 1n || s >= Curve.order) return false; - - let sInv = inverse(s, q); - if (sInv === undefined) throw Error('impossible'); - let u1 = mod(msgHash * sInv, q); - let u2 = mod(r * sInv, q); - - let X = Curve.add(Curve.scale(Curve.one, u1), Curve.scale(QA, u2)); - if (Curve.equal(X, Curve.zero)) return false; - - return mod(X.x, q) === r; -} From c85c5eaabdc36b17f0de2a6f61a1818df2915e81 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:45:24 +0100 Subject: [PATCH 0938/1215] tests compile --- src/lib/foreign-curve.ts | 10 +++++----- src/lib/foreign-curve.unit-test.ts | 8 ++++---- src/lib/foreign-ecdsa.unit-test.ts | 5 ++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index f5c7151037..d6db20b814 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -103,9 +103,9 @@ function createForeignCurve(params: CurveParams) { static generator = new ForeignCurve(params.generator); static modulus = params.modulus; - get modulus() { - return params.modulus; - } + // get modulus() { + // return params.modulus; + // } /** * Checks whether this curve point is constant. @@ -136,7 +136,7 @@ function createForeignCurve(params: CurveParams) { let z = ConstantCurve.add(toConstant(this), toConstant(h_)); return new ForeignCurve(z); } - let p = EllipticCurve.add(toPoint(this), toPoint(h_), this.modulus); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), params.modulus); return new ForeignCurve(p); } @@ -148,7 +148,7 @@ function createForeignCurve(params: CurveParams) { let z = ConstantCurve.double(toConstant(this)); return new ForeignCurve(z); } - let p = EllipticCurve.double(toPoint(this), this.modulus); + let p = EllipticCurve.double(toPoint(this), params.modulus); return new ForeignCurve(p); } diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index b7c1ebf8e8..5288500cf8 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -3,9 +3,10 @@ import { Fq } from '../bindings/crypto/finite_field.js'; import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; import { Provable } from './provable.js'; import { Field } from './field.js'; -import { vestaParams } from './foreign-curve-params.js'; +import { Crypto } from './crypto.js'; -class Vesta extends createForeignCurve(vestaParams) {} +class Vesta extends createForeignCurve(Crypto.CurveParams.Vesta) {} +class Fp extends Vesta.Scalar {} let g = { x: Fq.negate(1n), y: 2n, infinity: false }; let h = V.toAffine(V.negate(V.double(V.add(V.fromAffine(g), V.one)))); @@ -13,7 +14,6 @@ let scalar = Field.random().toBigInt(); let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); function main() { - Vesta.initialize(); let g0 = Provable.witness(Vesta, () => new Vesta(g)); let one = Provable.witness(Vesta, () => Vesta.generator); let h0 = g0.add(one).double().negate(); @@ -23,7 +23,7 @@ function main() { // TODO super slow // h0.assertInSubgroup(); - let scalar0 = Provable.witness(Field, () => new Field(scalar)).toBits(); + let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); // TODO super slow let p0 = h0.scale(scalar0); Provable.assertEqual(Vesta, p0, new Vesta(p)); diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index fd6fd69062..afd1a06a6a 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -1,9 +1,9 @@ import { createEcdsa } from './foreign-ecdsa.js'; -import { secp256k1Params } from './foreign-curve-params.js'; import { createForeignCurve } from './foreign-curve.js'; import { Provable } from './provable.js'; +import { Crypto } from './crypto.js'; -class Secp256k1 extends createForeignCurve(secp256k1Params) {} +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} class EthSignature extends createEcdsa(Secp256k1) {} @@ -22,7 +22,6 @@ let msgHash = ); function main() { - Secp256k1.initialize(); let signature0 = Provable.witness(EthSignature, () => signature); signature0.verify(msgHash, publicKey); } From 58400552d74b2166a445de978375433f3beefb19 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 30 Nov 2023 14:19:04 -0800 Subject: [PATCH 0939/1215] chore(mina): update mina submodule to latest commit in o1js-main branch --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 587a888ada..ae94ff0180 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 587a888ada0dadd92092b7f2616cf015a5305e56 +Subproject commit ae94ff0180d9e4653cbfb32c527fa7ab22423f19 From d9353d4eeb8378c2340f3aecf3cee089ddea6580 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 09:20:59 +0100 Subject: [PATCH 0940/1215] remove unnecessary helper --- src/lib/circuit_value.ts | 6 ------ src/lib/provable.ts | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 85793721b3..b799c20842 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -40,7 +40,6 @@ export { cloneCircuitValue, circuitValueEquals, toConstant, - isConstant, InferProvable, HashInput, InferJson, @@ -689,8 +688,3 @@ function toConstant(type: Provable, value: T): T { type.toAuxiliary(value) ); } - -function isConstant(type: FlexibleProvable, value: T): boolean; -function isConstant(type: Provable, value: T): boolean { - return type.toFields(value).every((x) => x.isConstant()); -} diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 4cf97b4f8a..534818f16f 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -3,7 +3,6 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ -import { FieldVar } from './field.js'; import { Field, Bool } from './core.js'; import { Provable as Provable_, Snarky } from '../snarky.js'; import type { FlexibleProvable, ProvableExtended } from './circuit_value.js'; @@ -127,7 +126,8 @@ const Provable = { * @example * ```ts * let x = Field(42); - * Provable.isConstant(x); // true + * Provable.isConstant(Field, x); // true + * ``` */ isConstant, /** From 6f3413a9e2fc62bcbd666957f61d617de0de316c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 09:21:57 +0100 Subject: [PATCH 0941/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 700639bc5d..1b0258a3b0 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 700639bc5df5b7e072446e3196bb0d3594fb32db +Subproject commit 1b0258a3b0e150a3b9a15b61c56115ef088a3865 From e9d17de1311b0f9a15bb0d222125113619839c7d Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 09:22:33 +0100 Subject: [PATCH 0942/1215] move foreign curve class outside class factory --- src/lib/foreign-curve.ts | 427 +++++++++++++++-------------- src/lib/foreign-curve.unit-test.ts | 8 +- src/lib/foreign-ecdsa.ts | 15 +- 3 files changed, 227 insertions(+), 223 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index d6db20b814..9809c5bebe 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -1,25 +1,217 @@ import { CurveParams, + CurveAffine, createCurveAffine, } from '../bindings/crypto/elliptic_curve.js'; -import { Snarky } from '../snarky.js'; -import { Bool } from './bool.js'; import type { Group } from './group.js'; -import { Struct, isConstant } from './circuit_value.js'; +import { ProvableExtended } from './circuit_value.js'; import { AlmostForeignField, createForeignField } from './foreign-field.js'; -import { MlBoolArray } from './ml/fields.js'; import { EllipticCurve, Point } from './gadgets/elliptic-curve.js'; import { Field3 } from './gadgets/foreign-field.js'; +import { assert } from './gadgets/common.js'; +import { Provable } from './provable.js'; +import { provableFromClass } from '../bindings/lib/provable-snarky.js'; // external API -export { createForeignCurve }; +export { createForeignCurve, ForeignCurve }; -// internal API -export { ForeignCurveClass }; +type FlexiblePoint = { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; +}; +function toPoint({ x, y }: ForeignCurve): Point { + return { x: x.value, y: y.value }; +} + +class ForeignCurve { + x: AlmostForeignField; + y: AlmostForeignField; + + /** + * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. + * @example + * ```ts + * let x = new ForeignCurve({ x: 1n, y: 1n }); + * ``` + * + * **Warning**: This fails for a constant input which does not represent an actual point on the curve. + */ + constructor(g: { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; + }) { + this.x = new this.Constructor.Field(g.x); + this.y = new this.Constructor.Field(g.y); + // don't allow constants that aren't on the curve + if (this.isConstant()) this.assertOnCurve(); + } + + /** + * Coerce the input to a {@link ForeignCurve}. + */ + static from(g: ForeignCurve | FlexiblePoint) { + if (g instanceof this) return g; + return new this(g); + } + + static get generator() { + return new this(this.Bigint.one); + } + static get modulus() { + return this.Bigint.modulus; + } + get modulus() { + return this.Constructor.Bigint.modulus; + } + + /** + * Checks whether this curve point is constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Provable.isConstant(this.Constructor.provable, this); + } + + /** + * Convert this curve point to a point with bigint coordinates. + */ + toBigint() { + // TODO make `infinity` not required in bigint curve APIs + return { x: this.x.toBigInt(), y: this.y.toBigInt(), infinity: false }; + } + + /** + * Elliptic curve addition. + */ + add(h: ForeignCurve | FlexiblePoint) { + let h_ = this.Constructor.from(h); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), this.modulus); + return new this.Constructor(p); + } + + /** + * Elliptic curve doubling. + */ + double() { + let p = EllipticCurve.double(toPoint(this), this.modulus); + return new this.Constructor(p); + } + + /** + * Elliptic curve negation. + */ + negate(): ForeignCurve { + throw Error('unimplemented'); + } + + static assertOnCurve(g: ForeignCurve) { + if (g.isConstant()) { + let isOnCurve = this.Bigint.isOnCurve(g.toBigint()); + if (!isOnCurve) + throw Error( + `${this.name}.assertOnCurve(): ${JSON.stringify( + this.provable.toJSON(g) + )} is not on the curve.` + ); + return; + } + throw Error('unimplemented'); + } + + /** + * Assert that this point lies on the elliptic curve, which means it satisfies the equation + * y^2 = x^3 + ax + b + */ + assertOnCurve() { + this.Constructor.assertOnCurve(this); + } + + /** + * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. + */ + scale(scalar: AlmostForeignField | bigint | number) { + let scalar_ = this.Constructor.Scalar.from(scalar); + if (this.isConstant() && scalar_.isConstant()) { + let z = this.Constructor.Bigint.scale( + this.toBigint(), + scalar_.toBigInt() + ); + return new this.Constructor(z); + } + let p = EllipticCurve.multiScalarMul( + this.Constructor.Bigint, + [scalar_.value], + [toPoint(this)], + [{ windowSize: 3 }] + ); + return new this.Constructor(p); + } + + static assertInSubgroup(g: ForeignCurve) { + if (g.isConstant()) { + let isInGroup = this.Bigint.isInSubgroup(g.toBigint()); + if (!isInGroup) + throw Error( + `${this.name}.assertInSubgroup(): ${JSON.stringify( + this.provable.toJSON(g) + )} is not in the target subgroup.` + ); + return; + } + throw Error('unimplemented'); + } + + /** + * Assert than this point lies in the subgroup defined by order*P = 0, + * by performing the scalar multiplication. + */ + assertInSubgroup() { + this.Constructor.assertInSubgroup(this); + } + + /** + * Check that this is a valid element of the target subgroup of the curve: + * - Use {@link assertOnCurve()} to check that the point lies on the curve + * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. + * + * **Exception**: If {@link createForeignCurve} is called with `{ unsafe: true }`, + * we don't check that curve elements are valid by default. + */ + static check(g: ForeignCurve) { + // check that x, y are valid field elements + this.Field.check(g.x); + this.Field.check(g.y); + this.assertOnCurve(g); + if (this.Bigint.hasCofactor) this.assertInSubgroup(g); + } -type Affine = { x: AlmostForeignField; y: AlmostForeignField }; + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof ForeignCurve; + } + static _Bigint?: CurveAffine; + static _Field?: typeof AlmostForeignField; + static _Scalar?: typeof AlmostForeignField; + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignCurve not initialized'); + return this._Bigint; + } + static get Field() { + assert(this._Field !== undefined, 'ForeignCurve not initialized'); + return this._Field; + } + static get Scalar() { + assert(this._Scalar !== undefined, 'ForeignCurve not initialized'); + return this._Scalar; + } -type ForeignCurveClass = ReturnType; + static _provable?: ProvableExtended; + static get provable() { + assert(this._provable !== undefined, 'ForeignCurve not initialized'); + return this._provable; + } +} /** * Create a class representing an elliptic curve group, which is different from the native {@link Group}. @@ -44,210 +236,21 @@ type ForeignCurveClass = ReturnType; * @param options * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. */ -function createForeignCurve(params: CurveParams) { - const Curve = createCurveAffine(params); - - const BaseFieldUnreduced = createForeignField(params.modulus); - const ScalarFieldUnreduced = createForeignField(params.order); - class BaseField extends BaseFieldUnreduced.AlmostReduced {} - class ScalarField extends ScalarFieldUnreduced.AlmostReduced {} - - // this is necessary to simplify the type of ForeignCurve, to avoid - // TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. - const Affine: Struct = Struct({ - x: BaseField.provable, - y: BaseField.provable, - }); - - const ConstantCurve = createCurveAffine(params); - - function toBigint(g: Affine) { - return { x: g.x.toBigInt(), y: g.y.toBigInt() }; - } - function toConstant(g: Affine) { - return { ...toBigint(g), infinity: false }; - } - - class ForeignCurve extends Affine { - /** - * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. - * @example - * ```ts - * let x = new ForeignCurve({ x: 1n, y: 1n }); - * ``` - * - * **Warning**: This fails for a constant input which does not represent an actual point on the curve. - */ - constructor(g: { - x: BaseField | Field3 | bigint | number; - y: BaseField | Field3 | bigint | number; - }) { - let x = new BaseField(g.x); - let y = new BaseField(g.y); - super({ x, y }); - // don't allow constants that aren't on the curve - if (this.isConstant()) this.assertOnCurve(); - } - - /** - * Coerce the input to a {@link ForeignCurve}. - */ - static from( - g: - | ForeignCurve - | { x: BaseField | bigint | number; y: BaseField | bigint | number } - ) { - if (g instanceof ForeignCurve) return g; - return new ForeignCurve(g); - } - - static generator = new ForeignCurve(params.generator); - static modulus = params.modulus; - // get modulus() { - // return params.modulus; - // } - - /** - * Checks whether this curve point is constant. - * - * See {@link FieldVar} to understand constants vs variables. - */ - isConstant() { - return isConstant(ForeignCurve, this); - } - - /** - * Convert this curve point to a point with bigint coordinates. - */ - toBigint() { - return { x: this.x.toBigInt(), y: this.y.toBigInt() }; - } - - /** - * Elliptic curve addition. - */ - add( - h: - | ForeignCurve - | { x: BaseField | bigint | number; y: BaseField | bigint | number } - ) { - let h_ = ForeignCurve.from(h); - if (this.isConstant() && h_.isConstant()) { - let z = ConstantCurve.add(toConstant(this), toConstant(h_)); - return new ForeignCurve(z); - } - let p = EllipticCurve.add(toPoint(this), toPoint(h_), params.modulus); - return new ForeignCurve(p); - } - - /** - * Elliptic curve doubling. - */ - double() { - if (this.isConstant()) { - let z = ConstantCurve.double(toConstant(this)); - return new ForeignCurve(z); - } - let p = EllipticCurve.double(toPoint(this), params.modulus); - return new ForeignCurve(p); - } +function createForeignCurve(params: CurveParams): typeof ForeignCurve { + const FieldUnreduced = createForeignField(params.modulus); + const ScalarUnreduced = createForeignField(params.order); + class Field extends FieldUnreduced.AlmostReduced {} + class Scalar extends ScalarUnreduced.AlmostReduced {} - /** - * Elliptic curve negation. - */ - negate() { - if (this.isConstant()) { - let z = ConstantCurve.negate(toConstant(this)); - return new ForeignCurve(z); - } - throw Error('unimplemented'); - } - - static assertOnCurve(g: Affine) { - if (isConstant(ForeignCurve, g)) { - let isOnCurve = ConstantCurve.isOnCurve(toConstant(g)); - if (!isOnCurve) - throw Error( - `${this.name}.assertOnCurve(): ${JSON.stringify( - ForeignCurve.toJSON(g) - )} is not on the curve.` - ); - return; - } - throw Error('unimplemented'); - } - - /** - * Assert that this point lies on the elliptic curve, which means it satisfies the equation - * y^2 = x^3 + ax + b - */ - assertOnCurve() { - ForeignCurve.assertOnCurve(this); - } - - // TODO wrap this in a `Scalar` type which is a Bool array under the hood? - /** - * Elliptic curve scalar multiplication, where the scalar is represented as a little-endian - * array of bits, and each bit is represented by a {@link Bool}. - */ - scale(scalar: ScalarField) { - if (this.isConstant() && scalar.isConstant()) { - let scalar0 = scalar.toBigInt(); - let z = ConstantCurve.scale(toConstant(this), scalar0); - return new ForeignCurve(z); - } - let p = EllipticCurve.multiScalarMul( - Curve, - [scalar.value], - [toPoint(this)] - ); - return new ForeignCurve(p); - } - - static assertInSubgroup(g: Affine) { - if (isConstant(Affine, g)) { - let isInGroup = ConstantCurve.isInSubgroup(toConstant(g)); - if (!isInGroup) - throw Error( - `${this.name}.assertInSubgroup(): ${JSON.stringify( - g - )} is not in the target subgroup.` - ); - return; - } - throw Error('unimplemented'); - } - - /** - * Assert than this point lies in the subgroup defined by order*P = 0, - * by performing the scalar multiplication. - */ - assertInSubgroup() { - ForeignCurve.assertInSubgroup(this); - } - - /** - * Check that this is a valid element of the target subgroup of the curve: - * - Use {@link assertOnCurve()} to check that the point lies on the curve - * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. - * - * **Exception**: If {@link createForeignCurve} is called with `{ unsafe: true }`, - * we don't check that curve elements are valid by default. - */ - static check(g: Affine) { - super.check(g); // check that x, y are valid field elements - ForeignCurve.assertOnCurve(g); - if (ConstantCurve.hasCofactor) ForeignCurve.assertInSubgroup(g); - } - - static BaseField = BaseField; - static Scalar = ScalarField; - static Bigint = ConstantCurve; + class Curve extends ForeignCurve { + static _Bigint = createCurveAffine(params); + static _Field = Field; + static _Scalar = Scalar; + static _provable = provableFromClass(Curve, { + x: Field.provable, + y: Field.provable, + }); } - return ForeignCurve; -} - -function toPoint({ x, y }: Affine): Point { - return { x: x.value, y: y.value }; + return Curve; } diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 5288500cf8..425679ebfb 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -14,10 +14,10 @@ let scalar = Field.random().toBigInt(); let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); function main() { - let g0 = Provable.witness(Vesta, () => new Vesta(g)); - let one = Provable.witness(Vesta, () => Vesta.generator); + let g0 = Provable.witness(Vesta.provable, () => new Vesta(g)); + let one = Provable.witness(Vesta.provable, () => Vesta.generator); let h0 = g0.add(one).double().negate(); - Provable.assertEqual(Vesta, h0, new Vesta(h)); + Provable.assertEqual(Vesta.provable, h0, new Vesta(h)); h0.assertOnCurve(); // TODO super slow @@ -26,7 +26,7 @@ function main() { let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); // TODO super slow let p0 = h0.scale(scalar0); - Provable.assertEqual(Vesta, p0, new Vesta(p)); + Provable.assertEqual(Vesta.provable, p0, new Vesta(p)); } console.time('running constant version'); diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 129f4114b3..5c4dba5061 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,8 +1,9 @@ import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; -import { Struct, isConstant } from './circuit_value.js'; -import { ForeignCurveClass, createForeignCurve } from './foreign-curve.js'; +import { Struct } from './circuit_value.js'; +import { ForeignCurve, createForeignCurve } from './foreign-curve.js'; import { AlmostForeignField } from './foreign-field.js'; import { verifyEcdsaConstant } from './gadgets/elliptic-curve.js'; +import { Provable } from './provable.js'; // external API export { createEcdsa }; @@ -13,12 +14,12 @@ type Signature = { r: AlmostForeignField; s: AlmostForeignField }; * Returns a class {@link EcdsaSignature} enabling to parse and verify ECDSA signature in provable code, * for the given curve. */ -function createEcdsa(curve: CurveParams | ForeignCurveClass) { - let Curve0: ForeignCurveClass = +function createEcdsa(curve: CurveParams | typeof ForeignCurve) { + let Curve0: typeof ForeignCurve = 'b' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} class Scalar extends Curve.Scalar {} - class BaseField extends Curve.BaseField {} + class BaseField extends Curve.Field {} const Signature: Struct = Struct({ r: Scalar.provable, @@ -71,7 +72,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { let msgHash_ = Scalar.from(msgHash); let publicKey_ = Curve.from(publicKey); - if (isConstant(Signature, this)) { + if (Provable.isConstant(Signature, this)) { let isValid = verifyEcdsaConstant( Curve.Bigint, { r: this.r.toBigInt(), s: this.s.toBigInt() }, @@ -90,7 +91,7 @@ function createEcdsa(curve: CurveParams | ForeignCurveClass) { * Check that r, s are valid scalars and both are non-zero */ static check(signature: { r: Scalar; s: Scalar }) { - if (isConstant(Signature, signature)) { + if (Provable.isConstant(Signature, signature)) { super.check(signature); // check valid scalars if (signature.r.toBigInt() === 0n) throw Error(`${this.name}.check(): r must be non-zero`); From 13081ccece847a628b50e24b22c91755c85bb44a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 09:58:49 +0100 Subject: [PATCH 0943/1215] minor --- src/lib/foreign-curve.ts | 6 ++++-- src/lib/group.ts | 12 ++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 9809c5bebe..b4b6886940 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -77,8 +77,10 @@ class ForeignCurve { * Convert this curve point to a point with bigint coordinates. */ toBigint() { - // TODO make `infinity` not required in bigint curve APIs - return { x: this.x.toBigInt(), y: this.y.toBigInt(), infinity: false }; + return this.Constructor.Bigint.fromNonzero({ + x: this.x.toBigInt(), + y: this.y.toBigInt(), + }); } /** diff --git a/src/lib/group.ts b/src/lib/group.ts index 89cf5bf249..6178032df6 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -2,7 +2,7 @@ import { Field, FieldVar, isField } from './field.js'; import { Scalar } from './scalar.js'; import { Snarky } from '../snarky.js'; import { Field as Fp } from '../provable/field-bigint.js'; -import { Pallas } from '../bindings/crypto/elliptic_curve.js'; +import { GroupAffine, Pallas } from '../bindings/crypto/elliptic_curve.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; @@ -73,15 +73,7 @@ class Group { } // helpers - static #fromAffine({ - x, - y, - infinity, - }: { - x: bigint; - y: bigint; - infinity: boolean; - }) { + static #fromAffine({ x, y, infinity }: GroupAffine) { return infinity ? Group.zero : new Group({ x, y }); } From 190f3e33b409a05fac36435bc3095fbee7f94675 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 10:20:40 +0100 Subject: [PATCH 0944/1215] negate --- src/lib/foreign-curve.ts | 2 +- src/lib/foreign-field.ts | 9 ++++++--- src/lib/gadgets/elliptic-curve.ts | 5 +++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index b4b6886940..9632603d7e 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -104,7 +104,7 @@ class ForeignCurve { * Elliptic curve negation. */ negate(): ForeignCurve { - throw Error('unimplemented'); + return new this.Constructor({ x: this.x, y: this.y.neg() }); } static assertOnCurve(g: ForeignCurve) { diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 337d4e0799..888974658f 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -103,7 +103,7 @@ class ForeignField { static from(x: bigint | number | string): CanonicalForeignField; static from(x: ForeignField | bigint | number | string): ForeignField; static from(x: ForeignField | bigint | number | string): ForeignField { - if (x instanceof ForeignField) return x; + if (x instanceof this) return x; return new this.Canonical(x); } @@ -208,8 +208,11 @@ class ForeignField { * ``` */ neg() { - let zero: ForeignField = this.Constructor.from(0n); - return this.Constructor.sum([zero, this], [-1]); + // this gets a special implementation because negation proves that the return value is almost reduced. + // it shows that r = f - x >= 0 or r = 0 (for x=0) over the integers, which implies r < f + // see also `Gadgets.ForeignField.assertLessThan()` + let xNeg = Gadgets.ForeignField.neg(this.value, this.modulus); + return new this.Constructor.AlmostReduced(xNeg); } /** diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 8d45b6e428..b49d63cd0e 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -29,6 +29,7 @@ export { verifyEcdsaConstant }; const EllipticCurve = { add, double, + negate, multiScalarMul, initialAggregator, }; @@ -140,6 +141,10 @@ function double(p1: Point, f: bigint) { return { x: x3, y: y3 }; } +function negate({ x, y }: Point, f: bigint) { + return { x, y: ForeignField.negate(y, f) }; +} + /** * Verify an ECDSA signature. * From d552bc217d5f643a0e221b599945b9137b8d8b8a Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:24:57 +0000 Subject: [PATCH 0945/1215] Keccak block transformation --- src/lib/keccak.ts | 217 ++++++++++++++++++++++++++++++++++++ src/lib/keccak.unit-test.ts | 105 +++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 src/lib/keccak.ts create mode 100644 src/lib/keccak.unit-test.ts diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts new file mode 100644 index 0000000000..2207e77a1d --- /dev/null +++ b/src/lib/keccak.ts @@ -0,0 +1,217 @@ +import { Field } from './field.js'; +import { Gadgets } from './gadgets/gadgets.js'; + +// KECCAK CONSTANTS + +// Length of the square matrix side of Keccak states +const KECCAK_DIM = 5; + +// Value `l` in Keccak, ranges from 0 to 6 and determines the lane width +const KECCAK_ELL = 6; + +// Width of a lane of the state, meaning the length of each word in bits (64) +const KECCAK_WORD = 2 ** KECCAK_ELL; + +// Number of bytes that fit in a word (8) +const BYTES_PER_WORD = KECCAK_WORD / 8; + +// Length of the state in bits, meaning the 5x5 matrix of words in bits (1600) +const KECCAK_STATE_LENGTH = KECCAK_DIM ** 2 * KECCAK_WORD; + +// Number of rounds of the Keccak permutation function depending on the value `l` (24) +const KECCAK_ROUNDS = 12 + 2 * KECCAK_ELL; + +// Creates the 5x5 table of rotation offset for Keccak modulo 64 +// | x \ y | 0 | 1 | 2 | 3 | 4 | +// | ----- | -- | -- | -- | -- | -- | +// | 0 | 0 | 36 | 3 | 41 | 18 | +// | 1 | 1 | 44 | 10 | 45 | 2 | +// | 2 | 62 | 6 | 43 | 15 | 61 | +// | 3 | 28 | 55 | 25 | 21 | 56 | +// | 4 | 27 | 20 | 39 | 8 | 14 | +const ROT_TABLE = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], +]; + +// Round constants for Keccak +// From https://keccak.team/files/Keccak-reference-3.0.pdf +const ROUND_CONSTANTS = [ + 0x0000000000000001n, + 0x0000000000008082n, + 0x800000000000808an, + 0x8000000080008000n, + 0x000000000000808bn, + 0x0000000080000001n, + 0x8000000080008081n, + 0x8000000000008009n, + 0x000000000000008an, + 0x0000000000000088n, + 0x0000000080008009n, + 0x000000008000000an, + 0x000000008000808bn, + 0x800000000000008bn, + 0x8000000000008089n, + 0x8000000000008003n, + 0x8000000000008002n, + 0x8000000000000080n, + 0x000000000000800an, + 0x800000008000000an, + 0x8000000080008081n, + 0x8000000000008080n, + 0x0000000080000001n, + 0x8000000080008008n, +].map(Field.from); + +// Return a keccak state where all lanes are equal to 0 +const getKeccakStateZeros = (): Field[][] => + Array.from(Array(KECCAK_DIM), (_) => Array(KECCAK_DIM).fill(Field.from(0))); + +// KECCAK HASH FUNCTION + +// ROUND TRANSFORMATION + +// First algrithm in the compression step of Keccak for 64-bit words. +// C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4] +// D[x] = C[x-1] xor ROT(C[x+1], 1) +// E[x,y] = A[x,y] xor D[x] +// In the Keccak reference, it corresponds to the `theta` algorithm. +// We use the first index of the state array as the x coordinate and the second index as the y coordinate. +const theta = (state: Field[][]): Field[][] => { + const stateA = state; + + // XOR the elements of each row together + // for all x in {0..4}: C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4] + const stateC = stateA.map((row) => + row.reduce((acc, next) => Gadgets.xor(acc, next, KECCAK_WORD)) + ); + + // for all x in {0..4}: D[x] = C[x-1] xor ROT(C[x+1], 1) + const stateD = Array.from({ length: KECCAK_DIM }, (_, x) => + Gadgets.xor( + stateC[(x + KECCAK_DIM - 1) % KECCAK_DIM], + // using (x + m mod m) to avoid negative values + Gadgets.rotate(stateC[(x + 1) % KECCAK_DIM], 1, 'left'), + KECCAK_WORD + ) + ); + + // for all x in {0..4} and y in {0..4}: E[x,y] = A[x,y] xor D[x] + const stateE = stateA.map((row, index) => + row.map((elem) => Gadgets.xor(elem, stateD[index], KECCAK_WORD)) + ); + + return stateE; +}; + +// Second and third steps in the compression step of Keccak for 64-bit words. +// B[y,2x+3y] = ROT(E[x,y], r[x,y]) +// which is equivalent to the `rho` algorithm followed by the `pi` algorithm in the Keccak reference as follows: +// rho: +// A[0,0] = a[0,0] +// | x | = | 1 | +// | y | = | 0 | +// for t = 0 to 23 do +// A[x,y] = ROT(a[x,y], (t+1)(t+2)/2 mod 64))) +// | x | = | 0 1 | | x | +// | | = | | * | | +// | y | = | 2 3 | | y | +// end for +// pi: +// for x = 0 to 4 do +// for y = 0 to 4 do +// | X | = | 0 1 | | x | +// | | = | | * | | +// | Y | = | 2 3 | | y | +// A[X,Y] = a[x,y] +// end for +// end for +// We use the first index of the state array as the x coordinate and the second index as the y coordinate. +function piRho(state: Field[][]): Field[][] { + const stateE = state; + const stateB: Field[][] = getKeccakStateZeros(); + + // for all x in {0..4} and y in {0..4}: B[y,2x+3y] = ROT(E[x,y], r[x,y]) + for (let x = 0; x < KECCAK_DIM; x++) { + for (let y = 0; y < KECCAK_DIM; y++) { + // No need to use mod since this is always positive + stateB[y][(2 * x + 3 * y) % KECCAK_DIM] = Gadgets.rotate( + stateE[x][y], + ROT_TABLE[x][y], + 'left' + ); + } + } + + return stateB; +} + +// Fourth step of the compression function of Keccak for 64-bit words. +// F[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y]) +// It corresponds to the chi algorithm in the Keccak reference. +// for y = 0 to 4 do +// for x = 0 to 4 do +// A[x,y] = a[x,y] xor ((not a[x+1,y]) and a[x+2,y]) +// end for +// end for +function chi(state: Field[][]): Field[][] { + const stateB = state; + const stateF = getKeccakStateZeros(); + + // for all x in {0..4} and y in {0..4}: F[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y]) + for (let x = 0; x < KECCAK_DIM; x++) { + for (let y = 0; y < KECCAK_DIM; y++) { + stateF[x][y] = Gadgets.xor( + stateB[x][y], + Gadgets.and( + // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 + Gadgets.not(stateB[(x + 1) % 5][y], KECCAK_WORD, false), + stateB[(x + 2) % 5][y], + KECCAK_WORD + ), + KECCAK_WORD + ); + } + } + + return stateF; +} + +// Fifth step of the permutation function of Keccak for 64-bit words. +// It takes the word located at the position (0,0) of the state and XORs it with the round constant. +function iota(state: Field[][], rc: Field): Field[][] { + const stateG = state; + + stateG[0][0] = Gadgets.xor(stateG[0][0], rc, KECCAK_WORD); + + return stateG; +} + +// One round of the Keccak permutation function. +// iota o chi o pi o rho o theta +function round(state: Field[][], rc: Field): Field[][] { + const stateA = state; + const stateE = theta(stateA); + const stateB = piRho(stateE); + const stateF = chi(stateB); + const stateD = iota(stateF, rc); + return stateD; +} + +// Keccak permutation function with a constant number of rounds +function permutation(state: Field[][], rc: Field[]): Field[][] { + return rc.reduce( + (currentState, rcValue) => round(currentState, rcValue), + state + ); +} + +// TESTING + +const blockTransformation = (state: Field[][]): Field[][] => + permutation(state, ROUND_CONSTANTS); + +export { ROUND_CONSTANTS, theta, piRho, chi, iota, round, blockTransformation }; diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts new file mode 100644 index 0000000000..2efdbc3957 --- /dev/null +++ b/src/lib/keccak.unit-test.ts @@ -0,0 +1,105 @@ +import { Field } from './field.js'; +import { Provable } from './provable.js'; +import { ZkProgram } from './proof_system.js'; +import { constraintSystem, print } from './testing/constraint-system.js'; +import { + ROUND_CONSTANTS, + theta, + piRho, + chi, + iota, + round, + blockTransformation, +} from './keccak.js'; + +const KECCAK_TEST_STATE = [ + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 0, 1, 0], + [0, 1, 0, 0, 0], +].map((row) => row.map((elem) => Field.from(elem))); + +let KeccakBlockTransformation = ZkProgram({ + name: 'KeccakBlockTransformation', + publicInput: Provable.Array(Provable.Array(Field, 5), 5), + publicOutput: Provable.Array(Provable.Array(Field, 5), 5), + + methods: { + Theta: { + privateInputs: [], + method(input: Field[][]) { + return theta(input); + }, + }, + PiRho: { + privateInputs: [], + method(input: Field[][]) { + return piRho(input); + }, + }, + Chi: { + privateInputs: [], + method(input: Field[][]) { + return chi(input); + }, + }, + Iota: { + privateInputs: [], + method(input: Field[][]) { + return iota(input, ROUND_CONSTANTS[0]); + }, + }, + Round: { + privateInputs: [], + method(input: Field[][]) { + return round(input, ROUND_CONSTANTS[0]); + }, + }, + BlockTransformation: { + privateInputs: [], + method(input: Field[][]) { + return blockTransformation(input); + }, + }, + }, +}); + +// constraintSystem.fromZkProgram( +// KeccakBlockTransformation, +// 'BlockTransformation', +// print +// ); + +console.log('KECCAK_TEST_STATE: ', KECCAK_TEST_STATE.toString()); + +console.log('Compiling...'); +await KeccakBlockTransformation.compile(); +console.log('Done!'); +console.log('Generating proof...'); +let proof0 = await KeccakBlockTransformation.BlockTransformation( + KECCAK_TEST_STATE +); +console.log('Done!'); +console.log('Output:', proof0.publicOutput.toString()); +console.log('Verifying...'); +proof0.verify(); +console.log('Done!'); + +/* +[RUST IMPLEMENTATION OUTPUT](https://github.com/BaldyAsh/keccak-rust) + +INPUT: +[[0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 0, 1, 0], + [0, 1, 0, 0, 0]] + +OUTPUT: +[[8771753707458093707, 14139250443469741764, 11827767624278131459, 2757454755833177578, 5758014717183214102], +[3389583698920935946, 1287099063347104936, 15030403046357116816, 17185756281681305858, 9708367831595350450], +[1416127551095004411, 16037937966823201128, 9518790688640222300, 1997971396112921437, 4893561083608951508], +[8048617297177300085, 10306645194383020789, 2789881727527423094, 7603160281577405588, 12935834807086847890], +[9476112750389234330, 13193683191463706918, 4460519148532423021, 7183125267124224670, 1393214916959060614]] +*/ From 8c09689689c5e65d6309c22f29e21e0f7976ab4e Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 10:40:12 +0100 Subject: [PATCH 0946/1215] support message in foreign field assertMul --- src/lib/gadgets/foreign-field.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index a8fdabc301..f089012e51 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -237,7 +237,8 @@ function assertMulInternal( x: Field3, y: Field3, xy: Field3 | Field2, - f: bigint + f: bigint, + message?: string ) { let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); @@ -247,12 +248,12 @@ function assertMulInternal( // bind remainder to input xy if (xy.length === 2) { let [xy01, xy2] = xy; - r01.assertEquals(xy01); - r2.assertEquals(xy2); + r01.assertEquals(xy01, message); + r2.assertEquals(xy2, message); } else { let xy01 = xy[0].add(xy[1].mul(1n << l)); - r01.assertEquals(xy01); - r2.assertEquals(xy[2]); + r01.assertEquals(xy01, message); + r2.assertEquals(xy[2], message); } } @@ -474,7 +475,8 @@ function assertMul( x: Field3 | Sum, y: Field3 | Sum, xy: Field3 | Sum, - f: bigint + f: bigint, + message?: string ) { x = Sum.fromUnfinished(x); y = Sum.fromUnfinished(y); @@ -505,11 +507,14 @@ function assertMul( let x_ = Field3.toBigint(x0); let y_ = Field3.toBigint(y0); let xy_ = Field3.toBigint(xy0); - assert(mod(x_ * y_, f) === xy_, 'incorrect multiplication result'); + assert( + mod(x_ * y_, f) === xy_, + message ?? 'assertMul(): incorrect multiplication result' + ); return; } - assertMulInternal(x0, y0, xy0, f); + assertMulInternal(x0, y0, xy0, f, message); } class Sum { From cd9193e08fe33bf586f35203aac3df8eaeea2866 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 10:40:20 +0100 Subject: [PATCH 0947/1215] assert on curve --- src/lib/foreign-curve.ts | 39 +++++++++++++++++-------------- src/lib/gadgets/elliptic-curve.ts | 13 +++++++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 9632603d7e..fa13a29d5c 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -108,17 +108,7 @@ class ForeignCurve { } static assertOnCurve(g: ForeignCurve) { - if (g.isConstant()) { - let isOnCurve = this.Bigint.isOnCurve(g.toBigint()); - if (!isOnCurve) - throw Error( - `${this.name}.assertOnCurve(): ${JSON.stringify( - this.provable.toJSON(g) - )} is not on the curve.` - ); - return; - } - throw Error('unimplemented'); + EllipticCurve.assertOnCurve(toPoint(g), this.Bigint.modulus, this.Bigint.b); } /** @@ -174,14 +164,11 @@ class ForeignCurve { /** * Check that this is a valid element of the target subgroup of the curve: + * - Check that the coordinates are valid field elements * - Use {@link assertOnCurve()} to check that the point lies on the curve * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. - * - * **Exception**: If {@link createForeignCurve} is called with `{ unsafe: true }`, - * we don't check that curve elements are valid by default. */ static check(g: ForeignCurve) { - // check that x, y are valid field elements this.Field.check(g.x); this.Field.check(g.y); this.assertOnCurve(g); @@ -195,20 +182,32 @@ class ForeignCurve { static _Bigint?: CurveAffine; static _Field?: typeof AlmostForeignField; static _Scalar?: typeof AlmostForeignField; + static _provable?: ProvableExtended; + + /** + * Curve arithmetic on JS bigints. + */ static get Bigint() { assert(this._Bigint !== undefined, 'ForeignCurve not initialized'); return this._Bigint; } + /** + * The base field of this curve as a {@link ForeignField}. + */ static get Field() { assert(this._Field !== undefined, 'ForeignCurve not initialized'); return this._Field; } + /** + * The scalar field of this curve as a {@link ForeignField}. + */ static get Scalar() { assert(this._Scalar !== undefined, 'ForeignCurve not initialized'); return this._Scalar; } - - static _provable?: ProvableExtended; + /** + * `Provable` + */ static get provable() { assert(this._provable !== undefined, 'ForeignCurve not initialized'); return this._provable; @@ -244,8 +243,12 @@ function createForeignCurve(params: CurveParams): typeof ForeignCurve { class Field extends FieldUnreduced.AlmostReduced {} class Scalar extends ScalarUnreduced.AlmostReduced {} + const BigintCurve = createCurveAffine(params); + assert(BigintCurve.a === 0n, 'a !=0 is not supported'); + assert(!BigintCurve.hasCofactor, 'cofactor != 1 is not supported'); + class Curve extends ForeignCurve { - static _Bigint = createCurveAffine(params); + static _Bigint = BigintCurve; static _Field = Field; static _Scalar = Scalar; static _provable = provableFromClass(Curve, { diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index b49d63cd0e..03c69bf2b4 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -30,6 +30,7 @@ const EllipticCurve = { add, double, negate, + assertOnCurve, multiScalarMul, initialAggregator, }; @@ -145,6 +146,18 @@ function negate({ x, y }: Point, f: bigint) { return { x, y: ForeignField.negate(y, f) }; } +function assertOnCurve(p: Point, f: bigint, b: bigint) { + // TODO this assumes the curve has a == 0 + let { x, y } = p; + let x2 = ForeignField.mul(x, x, f); + let y2 = ForeignField.mul(y, y, f); + let y2MinusB = ForeignField.Sum(y2).sub(Field3.from(b)); + + // x^2 * x = y^2 - b + let message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; + ForeignField.assertMul(x2, x, y2MinusB, f, message); +} + /** * Verify an ECDSA signature. * From 722cfd642e814c410b9cf7398554c450cdeaf97a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 11:12:35 +0100 Subject: [PATCH 0948/1215] scale gadget --- src/lib/gadgets/elliptic-curve.ts | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 03c69bf2b4..ba92dc22bd 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -31,6 +31,7 @@ const EllipticCurve = { double, negate, assertOnCurve, + scale, multiScalarMul, initialAggregator, }; @@ -158,6 +159,38 @@ function assertOnCurve(p: Point, f: bigint, b: bigint) { ForeignField.assertMul(x2, x, y2MinusB, f, message); } +/** + * EC scalar multiplication, `scalar*point` + * + * The result is constrained to be not zero. + */ +function scale( + Curve: CurveAffine, + scalar: Field3, + point: Point, + config: { windowSize?: number; multiples?: Point[] } = { windowSize: 3 } +) { + // constant case + if (Field3.isConstant(scalar) && Point.isConstant(point)) { + let scalar_ = Field3.toBigint(scalar); + let p_ = Point.toBigint(point); + let scaled = Curve.scale(p_, scalar_); + return Point.from(scaled); + } + + // provable case + return multiScalarMul(Curve, [scalar], [point], [config]); +} + +// checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 +function assertInSubgroup(Curve: CurveAffine, p: Point) { + const order = Field3.from(Curve.order); + // [order]g = 0 + let orderG = scale(Curve, order, p); + // TODO support zero result from scale + throw Error('unimplemented'); +} + /** * Verify an ECDSA signature. * From 1ad26021b39076e229b02e936a8fae8b1492ea40 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 13:30:30 +0100 Subject: [PATCH 0949/1215] foreign field: split assert non zero into function --- src/lib/gadgets/foreign-field.ts | 34 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 4b92c54299..20b9b3a244 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -71,6 +71,30 @@ const ForeignField = { // (note: ffadd can't add higher multiples of (f - 1). it must always use an overflow of -1, except for x = 0 or 1) ForeignField.negate(x, f - 1n); }, + + /** + * prove that x != 0 mod f + * + * assumes that x is almost reduced mod f, so we know that x can at most be 0 or f, but not 2f, 3f,... + */ + assertNonZero(x: Field3, f: bigint) { + // constant case + if (Field3.isConstant(x)) { + assert(Field3.toBigint(x) !== 0n, 'assertNonZero: got x = 0'); + return; + } + // provable case + // check that x != 0 and x != f + let x01 = toVar(x[0].add(x[1].mul(1n << l))); + + // (x01, x2) != (0, 0) + x01.equals(0n).and(x[2].equals(0n)).assertFalse(); + // (x01, x2) != (f01, f2) + x01 + .equals(f & l2Mask) + .and(x[2].equals(f >> l2)) + .assertFalse(); + }, }; /** @@ -217,16 +241,8 @@ function divide( multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); if (!allowZeroOverZero) { - // assert that y != 0 mod f by checking that it doesn't equal 0 or f - // this works because we assume y[2] <= f2 - // TODO is this the most efficient way? - let y01 = y[0].add(y[1].mul(1n << l)); - y01.equals(0n).and(y[2].equals(0n)).assertFalse(); - let [f0, f1, f2] = split(f); - let f01 = combine2([f0, f1]); - y01.equals(f01).and(y[2].equals(f2)).assertFalse(); + ForeignField.assertNonZero(y, f); } - return z; } From 1c90b526cece020cc9ff795f4d61ac1542aba4fe Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 13:46:03 +0100 Subject: [PATCH 0950/1215] assertNotEquals -> equals --- src/lib/gadgets/foreign-field.ts | 34 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 20b9b3a244..a1583fe98f 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -6,6 +6,7 @@ import { mod, } from '../../bindings/crypto/finite_field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; +import { Bool } from '../bool.js'; import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; @@ -73,27 +74,32 @@ const ForeignField = { }, /** - * prove that x != 0 mod f + * check whether x = c mod f * - * assumes that x is almost reduced mod f, so we know that x can at most be 0 or f, but not 2f, 3f,... + * c is a constant, and we require c in [0, f) + * + * assumes that x is almost reduced mod f, so we know that x might be c or c + f, but not c + 2f, c + 3f, ... */ - assertNonZero(x: Field3, f: bigint) { + equals(x: Field3, c: bigint, f: bigint) { + assert(c >= 0n && c < f, 'equals: c must be in [0, f)'); + // constant case if (Field3.isConstant(x)) { - assert(Field3.toBigint(x) !== 0n, 'assertNonZero: got x = 0'); - return; + return new Bool(mod(Field3.toBigint(x), f) === c); } + // provable case - // check that x != 0 and x != f + // check whether x = 0 or x = f let x01 = toVar(x[0].add(x[1].mul(1n << l))); + let [c01, c2] = [c & l2Mask, c >> l2]; + let [cPlusF01, cPlusF2] = [(c + f) & l2Mask, (c + f) >> l2]; + + // (x01, x2) = (c01, c2) + let isC = x01.equals(c01).and(x[2].equals(c2)); + // (x01, x2) = (cPlusF01, cPlusF2) + let isCPlusF = x01.equals(cPlusF01).and(x[2].equals(cPlusF2)); - // (x01, x2) != (0, 0) - x01.equals(0n).and(x[2].equals(0n)).assertFalse(); - // (x01, x2) != (f01, f2) - x01 - .equals(f & l2Mask) - .and(x[2].equals(f >> l2)) - .assertFalse(); + return isC.or(isCPlusF); }, }; @@ -241,7 +247,7 @@ function divide( multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); if (!allowZeroOverZero) { - ForeignField.assertNonZero(y, f); + ForeignField.equals(y, 0n, f).assertFalse(); } return z; } From 7886bef437ad96679b226ad646091d71da1e324f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 13:49:34 +0100 Subject: [PATCH 0951/1215] fix final check in scalar mul --- src/lib/gadgets/elliptic-curve.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 8d45b6e428..c658039e0b 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -298,7 +298,13 @@ function multiScalarMul( // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); - Provable.equal(Point.provable, sum, Point.from(iaFinal)).assertFalse(); + let xEquals = ForeignField.equals(sum.x, iaFinal.x, Curve.modulus); + let yEquals = ForeignField.equals(sum.y, iaFinal.y, Curve.modulus); + let isZero = xEquals.and(yEquals); + + // TODO: return isZero instead of asserting here + isZero.assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); return sum; From ba0c36160b195d5063bee0a4c87a031e9fecec76 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 14:16:18 +0100 Subject: [PATCH 0952/1215] split out equals --- src/lib/gadgets/elliptic-curve.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index c658039e0b..20155c7e22 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -140,6 +140,14 @@ function double(p1: Point, f: bigint) { return { x: x3, y: y3 }; } +// check whether a point equals a constant point +// TODO implement the full case of two vars +function equals(p1: Point, p2: point, f: bigint) { + let xEquals = ForeignField.equals(p1.x, p2.x, f); + let yEquals = ForeignField.equals(p1.y, p2.y, f); + return xEquals.and(yEquals); +} + /** * Verify an ECDSA signature. * @@ -298,11 +306,7 @@ function multiScalarMul( // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); - let xEquals = ForeignField.equals(sum.x, iaFinal.x, Curve.modulus); - let yEquals = ForeignField.equals(sum.y, iaFinal.y, Curve.modulus); - let isZero = xEquals.and(yEquals); - - // TODO: return isZero instead of asserting here + let isZero = equals(sum, iaFinal, Curve.modulus); isZero.assertFalse(); sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); From dba58f372fddbcd610d39a430eedbde07ed9167a Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 14:29:12 +0100 Subject: [PATCH 0953/1215] vk update --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 6044f0bcf3..0297b30a04 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "339463a449bf05b95a8c33f61cfe8ce434517139ca76d46acd8156fa148fa17d", + "digest": "6e4078c6df944db119dc1656eb68e6c272c3f2e9cab6746913759537cbfcfa9", "methods": { "verifyEcdsa": { - "rows": 38836, - "digest": "1d54d1addf7630e3eb226959de232cc3" + "rows": 38846, + "digest": "892b0a1fad0f13d92ba6099cd54e6780" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAGIC/c5mlvJEtw3GmFSOhllzNMb5rze5XPBrZhVM6zUgjHCxrGo9kh7sUu/CJtpp5k56fIqgmrAEIdVJ3IloJAUgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgtRrSDSqUoCxu0FXG8VDNFSk+wPBfgTjvTmC9gbTjPgfr95gk0HAAeyDFGWwKsDvU4bkqsacYCSWVZ7OFcx2LDV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "27986645693165294918748566661718640959750969878894001222795403177870132383423" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJy9KmK2C+3hCZoGpDDhYsWLlly6udS3Uh6qYr0X1NU/Ns8UpTCq9fR7ST8OmK+DdktHHPQGmGD2FHfSOPBhRicgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AggZ9tT5WqF5mKwaoycspge8k5SYrr9T1DwdfhQHP86zGDZzvZlQGyPIvrcZ+bpTMc5+4GUl8mI5/IIZnX0cM0HV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "21152043351405736424630704375244880683906889913335338686836578165782029001213" } } } \ No newline at end of file From 4d54927d0b5c3094623cbee42cfab0efecab12d9 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:00:04 +0000 Subject: [PATCH 0954/1215] Fix comments and use constants --- src/lib/keccak.ts | 10 ++++------ src/lib/keccak.unit-test.ts | 5 +++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 2207e77a1d..1187d89b78 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -74,7 +74,7 @@ const getKeccakStateZeros = (): Field[][] => // ROUND TRANSFORMATION -// First algrithm in the compression step of Keccak for 64-bit words. +// First algorithm in the compression step of Keccak for 64-bit words. // C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4] // D[x] = C[x-1] xor ROT(C[x+1], 1) // E[x,y] = A[x,y] xor D[x] @@ -93,7 +93,6 @@ const theta = (state: Field[][]): Field[][] => { const stateD = Array.from({ length: KECCAK_DIM }, (_, x) => Gadgets.xor( stateC[(x + KECCAK_DIM - 1) % KECCAK_DIM], - // using (x + m mod m) to avoid negative values Gadgets.rotate(stateC[(x + 1) % KECCAK_DIM], 1, 'left'), KECCAK_WORD ) @@ -137,7 +136,6 @@ function piRho(state: Field[][]): Field[][] { // for all x in {0..4} and y in {0..4}: B[y,2x+3y] = ROT(E[x,y], r[x,y]) for (let x = 0; x < KECCAK_DIM; x++) { for (let y = 0; y < KECCAK_DIM; y++) { - // No need to use mod since this is always positive stateB[y][(2 * x + 3 * y) % KECCAK_DIM] = Gadgets.rotate( stateE[x][y], ROT_TABLE[x][y], @@ -168,8 +166,8 @@ function chi(state: Field[][]): Field[][] { stateB[x][y], Gadgets.and( // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 - Gadgets.not(stateB[(x + 1) % 5][y], KECCAK_WORD, false), - stateB[(x + 2) % 5][y], + Gadgets.not(stateB[(x + 1) % KECCAK_DIM][y], KECCAK_WORD, false), + stateB[(x + 2) % KECCAK_DIM][y], KECCAK_WORD ), KECCAK_WORD @@ -214,4 +212,4 @@ function permutation(state: Field[][], rc: Field[]): Field[][] { const blockTransformation = (state: Field[][]): Field[][] => permutation(state, ROUND_CONSTANTS); -export { ROUND_CONSTANTS, theta, piRho, chi, iota, round, blockTransformation }; +export { KECCAK_DIM, ROUND_CONSTANTS, theta, piRho, chi, iota, round, blockTransformation }; diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 2efdbc3957..1c20b2ec6f 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -3,6 +3,7 @@ import { Provable } from './provable.js'; import { ZkProgram } from './proof_system.js'; import { constraintSystem, print } from './testing/constraint-system.js'; import { + KECCAK_DIM, ROUND_CONSTANTS, theta, piRho, @@ -22,8 +23,8 @@ const KECCAK_TEST_STATE = [ let KeccakBlockTransformation = ZkProgram({ name: 'KeccakBlockTransformation', - publicInput: Provable.Array(Provable.Array(Field, 5), 5), - publicOutput: Provable.Array(Provable.Array(Field, 5), 5), + publicInput: Provable.Array(Provable.Array(Field, KECCAK_DIM), KECCAK_DIM), + publicOutput: Provable.Array(Provable.Array(Field, KECCAK_DIM), KECCAK_DIM), methods: { Theta: { From 3132cdcb0533aa4d83c18b9906043295e22701e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:33:11 +0100 Subject: [PATCH 0955/1215] foreign field: fix division type --- src/lib/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 55cd105160..06304095a7 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -450,7 +450,7 @@ class ForeignFieldWithMul extends ForeignField { * z.mul(y).assertEquals(x); * ``` */ - div(y: ForeignField | bigint | number) { + div(y: AlmostForeignField | bigint | number) { const p = this.modulus; let z = Gadgets.ForeignField.div(this.value, toLimbs(y, p), p); return new this.Constructor.AlmostReduced(z); From 5e3d6a2853d9a8aebbdce934b5569b2d42b4e17b Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:59:09 +0100 Subject: [PATCH 0956/1215] type fix --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 7f16f4ae9f..691ebbcbee 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -7,7 +7,7 @@ import { rangeCheck64, } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; -import { Field } from '../core.js'; +import { Field } from '../field.js'; import { ForeignField, Field3 } from './foreign-field.js'; export { Gadgets }; From cc4d3fce0155f73540ecd17f8fbc02cb3632e7bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:34 +0100 Subject: [PATCH 0957/1215] add range check 8 gadget --- src/lib/gadgets/gadgets.ts | 11 +++++++++++ src/lib/gadgets/range-check.ts | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b67ef58fd9..93f64cd482 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -5,6 +5,7 @@ import { compactMultiRangeCheck, multiRangeCheck, rangeCheck64, + rangeCheck8, } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../core.js'; @@ -39,6 +40,16 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, + + /** + * Asserts that the input value is in the range [0, 2^8). + * + * See {@link Gadgets.rangeCheck8} for analogous details and usage examples. + */ + rangeCheck8(x: Field) { + return rangeCheck8(x); + }, + /** * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 1e44afdaf9..147ef1a6a4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,8 +1,8 @@ import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { bitSlice, exists, toVar, toVars } from './common.js'; +import { assert, bitSlice, exists, toVar, toVars } from './common.js'; -export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; +export { rangeCheck64, rangeCheck8, multiRangeCheck, compactMultiRangeCheck }; export { l, l2, l3, lMask, l2Mask }; /** @@ -207,3 +207,19 @@ function rangeCheck1Helper(inputs: { [z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0] ); } + +function rangeCheck8(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 8n, + `rangeCheck8: expected field to fit in 8 bits, got ${x}` + ); + return; + } + + // check that x fits in 16 bits + x.rangeCheckHelper(16).assertEquals(x); + // check that 2^8 x fits in 16 bits + let x256 = x.mul(1 << 8).seal(); + x256.rangeCheckHelper(16).assertEquals(x256); +} From 2edfa18bf75642eb5bf17922dc457c186e5d83dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:45 +0100 Subject: [PATCH 0958/1215] range check 8 unit test --- src/lib/gadgets/range-check.unit-test.ts | 31 +++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 47aafbf592..1caea18f17 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -40,6 +40,13 @@ constraintSystem( ifNotAllConstant(withoutGenerics(equals(['RangeCheck0']))) ); +constraintSystem( + 'range check 8', + { from: [Field] }, + Gadgets.rangeCheck8, + ifNotAllConstant(withoutGenerics(equals(['EndoMulScalar', 'EndoMulScalar']))) +); + constraintSystem( 'multi-range check', { from: [Field, Field, Field] }, @@ -72,6 +79,12 @@ let RangeCheck = ZkProgram({ Gadgets.rangeCheck64(x); }, }, + check8: { + privateInputs: [Field], + method(x) { + Gadgets.rangeCheck8(x); + }, + }, checkMulti: { privateInputs: [Field, Field, Field], method(x, y, z) { @@ -91,8 +104,9 @@ let RangeCheck = ZkProgram({ await RangeCheck.compile(); // TODO: we use this as a test because there's no way to check custom gates quickly :( +const runs = 2; -await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs })( (x) => { assert(x < 1n << 64n); return true; @@ -103,9 +117,20 @@ await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( } ); +await equivalentAsync({ from: [maybeUint(8)], to: boolean }, { runs })( + (x) => { + assert(x < 1n << 8n); + return true; + }, + async (x) => { + let proof = await RangeCheck.check8(x); + return await RangeCheck.verify(proof); + } +); + await equivalentAsync( { from: [maybeUint(l), uint(l), uint(l)], to: boolean }, - { runs: 3 } + { runs } )( (x, y, z) => { assert(!(x >> l) && !(y >> l) && !(z >> l), 'multi: not out of range'); @@ -119,7 +144,7 @@ await equivalentAsync( await equivalentAsync( { from: [maybeUint(2n * l), uint(l)], to: boolean }, - { runs: 3 } + { runs } )( (xy, z) => { assert(!(xy >> (2n * l)) && !(z >> l), 'compact: not out of range'); From 5312b014616f1ec914e67517d0c181c0473f0f63 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:59 +0100 Subject: [PATCH 0959/1215] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd3e03aac..0f52c5178e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +### Added + +- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288 + ### Changed - Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. From 02f5f270d2b3a913b1d42057610d1bfe7e75a9fb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 11:04:43 +0100 Subject: [PATCH 0960/1215] fix doccomment --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 93f64cd482..7ee5f6c7c7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -44,7 +44,7 @@ const Gadgets = { /** * Asserts that the input value is in the range [0, 2^8). * - * See {@link Gadgets.rangeCheck8} for analogous details and usage examples. + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. */ rangeCheck8(x: Field) { return rangeCheck8(x); From 7b807284a2c596f361ef0bcc0213b96e1997732c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 11:09:18 +0100 Subject: [PATCH 0961/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1dd31581aa..13e06d7494 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1dd31581aacb3e6d76422063f052ea88c1451e1d +Subproject commit 13e06d7494a14e5bda0e2f92204edb48939c7a68 From d0b09b9c1d86061a3f6bea97b9086a4caf33d792 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 12:29:15 +0100 Subject: [PATCH 0962/1215] apply small review suggestions --- src/lib/gadgets/basic.ts | 4 ++-- src/lib/gadgets/ecdsa.unit-test.ts | 30 ++++++++---------------------- src/lib/gadgets/elliptic-curve.ts | 4 ++-- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index 6d534ac377..dfff5f1de3 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -24,7 +24,7 @@ function arrayGet(array: Field[], index: Field) { // witness result let a = existsOne(() => array[Number(i.toBigInt())].toBigInt()); - // we prove a === array[j] + zj*(i - j) for some zj, for all j. + // we prove a === array[j] + z[j]*(i - j) for some z[j], for all j. // setting j = i, this implies a === array[i] // thanks to our assumption that the index i is within bounds, we know that j = i for some j let n = array.length; @@ -36,7 +36,7 @@ function arrayGet(array: Field[], index: Field) { ); return zj ?? 0n; }); - // prove that zj*(i - j) === a - array[j] + // prove that z[j]*(i - j) === a - array[j] // TODO abstract this logic into a general-purpose assertMul() gadget, // which is able to use the constant coefficient // (snarky's assert_r1cs somehow leads to much more constraints than this) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 9cbcbe4e7d..33c1f8ce6f 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -33,7 +33,8 @@ for (let Curve of curves) { let scalar = foreignField(Curve.Scalar); let privateKey = uniformForeignField(Curve.Scalar); - let pseudoSignature = record({ + // correct signature shape, but independently random components, which will never form a valid signature + let badSignature = record({ signature: record({ r: scalar, s: scalar }), msg: scalar, publicKey: record({ x: field, y: field }), @@ -42,7 +43,7 @@ for (let Curve of curves) { let signatureInputs = record({ privateKey, msg: scalar }); let signature = map( - { from: signatureInputs, to: pseudoSignature }, + { from: signatureInputs, to: badSignature }, ({ privateKey, msg }) => { let publicKey = Curve.scale(Curve.one, privateKey); let signature = Ecdsa.sign(Curve, msg, privateKey); @@ -63,14 +64,14 @@ for (let Curve of curves) { ); // negative test - equivalentProvable({ from: [pseudoSignature], to: bool })( + equivalentProvable({ from: [badSignature], to: bool })( () => false, verify, 'invalid signature fails' ); // test against constant implementation, with both invalid and valid signatures - equivalentProvable({ from: [oneOf(signature, pseudoSignature)], to: bool })( + equivalentProvable({ from: [oneOf(signature, badSignature)], to: bool })( ({ signature, publicKey, msg }) => { return verifyEcdsaConstant(Curve, signature, msg, publicKey); }, @@ -102,20 +103,6 @@ let program = ZkProgram({ name: 'ecdsa', publicOutput: Bool, methods: { - scale: { - privateInputs: [], - method() { - let G = Point.from(Secp256k1.one); - let P = Provable.witness(Point.provable, () => publicKey); - let R = EllipticCurve.multiScalarMul( - Secp256k1, - [signature.s, signature.r], - [G, P], - [config.G, config.P] - ); - return new Bool(true); - }, - }, ecdsa: { privateInputs: [], method() { @@ -137,18 +124,17 @@ let program = ZkProgram({ }, }, }); -let main = program.rawMethods.ecdsa; console.time('ecdsa verify (constant)'); -main(); +program.rawMethods.ecdsa(); console.timeEnd('ecdsa verify (constant)'); console.time('ecdsa verify (witness gen / check)'); -Provable.runAndCheck(main); +Provable.runAndCheck(program.rawMethods.ecdsa); console.timeEnd('ecdsa verify (witness gen / check)'); console.time('ecdsa verify (build constraint system)'); -let cs = Provable.constraintSystem(main); +let cs = program.analyzeMethods().ecdsa; console.timeEnd('ecdsa verify (build constraint system)'); let gateTypes: Record = {}; diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 20155c7e22..8077248e89 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -154,7 +154,7 @@ function equals(p1: Point, p2: point, f: bigint) { * Details about the `config` parameter: * - For both the generator point `G` and public key `P`, `config` allows you to specify: * - the `windowSize` which is used in scalar multiplication for this point. - * flexibility is good because the optimal window size is different for constant and non-constant points. + * this flexibility is good because the optimal window size is different for constant and non-constant points. * empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. * our defaults reflect that the generator is always constant and the public key is variable in typical applications. * - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. @@ -475,7 +475,7 @@ function sliceField( let chunks = []; let sum = Field.from(0n); - // if there's a leftover chunk from a previous slizeField() call, we complete it + // if there's a leftover chunk from a previous sliceField() call, we complete it if (leftover !== undefined) { let { chunks: previous, leftoverSize: size } = leftover; let remainingChunk = Field.from(0n); From f68c1487a634fe0f1858aa2a1b0a5430c7ead34d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 12:41:42 +0100 Subject: [PATCH 0963/1215] support for a!=0 in double gadget --- src/lib/gadgets/elliptic-curve.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 8077248e89..49b6b7ef50 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -92,7 +92,7 @@ function add(p1: Point, p2: Point, f: bigint) { return { x: x3, y: y3 }; } -function double(p1: Point, f: bigint) { +function double(p1: Point, f: bigint, a: bigint) { let { x: x1, y: y1 } = p1; // constant case @@ -122,11 +122,11 @@ function double(p1: Point, f: bigint) { // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); - // 2*y1*m = 3*x1x1 - // TODO this assumes the curve has a == 0 + // 2*y1*m = 3*x1x1 + a let y1Times2 = ForeignField.Sum(y1).add(y1); - let x1x1Times3 = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); - ForeignField.assertMul(y1Times2, m, x1x1Times3, f); + let x1x1Times3PlusA = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); + if (a !== 0n) x1x1Times3PlusA = x1x1Times3PlusA.add(Field3.from(a)); + ForeignField.assertMul(y1Times2, m, x1x1Times3PlusA, f); // m^2 = 2*x1 + x3 let xSum = ForeignField.Sum(x1).add(x1).add(x3); @@ -300,7 +300,7 @@ function multiScalarMul( // jointly double all points // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) - sum = double(sum, Curve.modulus); + sum = double(sum, Curve.modulus, Curve.a); } // the sum is now 2^(b-1)*IA + sum_i s_i*P_i @@ -374,7 +374,7 @@ function getPointTable( table = [Point.from(Curve.zero), P]; if (n === 2) return table; - let Pi = double(P, Curve.modulus); + let Pi = double(P, Curve.modulus, Curve.a); table.push(Pi); for (let i = 3; i < n; i++) { Pi = add(Pi, P, Curve.modulus); From 124d9180a8b201865ece92379ac48d799bbb6b44 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 12:52:33 +0100 Subject: [PATCH 0964/1215] move ff equals gadget and handle an edge case --- src/lib/gadgets/foreign-field.ts | 66 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index a1583fe98f..42899a8655 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -73,34 +73,7 @@ const ForeignField = { ForeignField.negate(x, f - 1n); }, - /** - * check whether x = c mod f - * - * c is a constant, and we require c in [0, f) - * - * assumes that x is almost reduced mod f, so we know that x might be c or c + f, but not c + 2f, c + 3f, ... - */ - equals(x: Field3, c: bigint, f: bigint) { - assert(c >= 0n && c < f, 'equals: c must be in [0, f)'); - - // constant case - if (Field3.isConstant(x)) { - return new Bool(mod(Field3.toBigint(x), f) === c); - } - - // provable case - // check whether x = 0 or x = f - let x01 = toVar(x[0].add(x[1].mul(1n << l))); - let [c01, c2] = [c & l2Mask, c >> l2]; - let [cPlusF01, cPlusF2] = [(c + f) & l2Mask, (c + f) >> l2]; - - // (x01, x2) = (c01, c2) - let isC = x01.equals(c01).and(x[2].equals(c2)); - // (x01, x2) = (cPlusF01, cPlusF2) - let isCPlusF = x01.equals(cPlusF01).and(x[2].equals(cPlusF2)); - - return isC.or(isCPlusF); - }, + equals, }; /** @@ -410,6 +383,43 @@ function assertAlmostFieldElements(xs: Field3[], f: bigint) { } } +/** + * check whether x = c mod f + * + * c is a constant, and we require c in [0, f) + * + * assumes that x is almost reduced mod f, so we know that x might be c or c + f, but not c + 2f, c + 3f, ... + */ +function equals(x: Field3, c: bigint, f: bigint) { + assert(c >= 0n && c < f, 'equals: c must be in [0, f)'); + + // constant case + if (Field3.isConstant(x)) { + return new Bool(mod(Field3.toBigint(x), f) === c); + } + + // provable case + if (f >= 1n << l2) { + // check whether x = 0 or x = f + let x01 = toVar(x[0].add(x[1].mul(1n << l))); + let [c01, c2] = [c & l2Mask, c >> l2]; + let [cPlusF01, cPlusF2] = [(c + f) & l2Mask, (c + f) >> l2]; + + // (x01, x2) = (c01, c2) + let isC = x01.equals(c01).and(x[2].equals(c2)); + // (x01, x2) = (cPlusF01, cPlusF2) + let isCPlusF = x01.equals(cPlusF01).and(x[2].equals(cPlusF2)); + + return isC.or(isCPlusF); + } else { + // if f < 2^2l, the approach above doesn't work (we don't know from x[2] = 0 that x < 2f), + // so in that case we assert that x < f and then check whether it's equal to c + ForeignField.assertLessThan(x, f); + let x012 = toVar(x[0].add(x[1].mul(1n << l)).add(x[2].mul(1n << l2))); + return x012.equals(c); + } +} + const Field3 = { /** * Turn a bigint into a 3-tuple of Fields From 2495901b57c51958423f0694a514835590bc0f6f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 12:56:33 +0100 Subject: [PATCH 0965/1215] rename assertAlmostFieldElements --- src/lib/gadgets/elliptic-curve.ts | 4 +-- src/lib/gadgets/foreign-field.ts | 4 +-- src/lib/gadgets/foreign-field.unit-test.ts | 4 +-- src/lib/gadgets/gadgets.ts | 40 +++++++++++----------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 49b6b7ef50..c1ff875509 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -73,7 +73,7 @@ function add(p1: Point, p2: Point, f: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - ForeignField.assertAlmostFieldElements([m, x3, y3], f); + ForeignField.assertAlmostReduced([m, x3, y3], f); // (x1 - x2)*m = y1 - y2 let deltaX = ForeignField.Sum(x1).sub(x2); @@ -117,7 +117,7 @@ function double(p1: Point, f: bigint, a: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - ForeignField.assertAlmostFieldElements([m, x3, y3], f); + ForeignField.assertAlmostReduced([m, x3, y3], f); // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 42899a8655..f1929df99a 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -56,7 +56,7 @@ const ForeignField = { div: divide, assertMul, - assertAlmostFieldElements, + assertAlmostReduced, assertLessThan(x: Field3, f: bigint) { assert(f > 0n, 'assertLessThan: upper bound must be positive'); @@ -363,7 +363,7 @@ function weakBound(x: Field, f: bigint) { * Apply range checks and weak bounds checks to a list of Field3s. * Optimal if the list length is a multiple of 3. */ -function assertAlmostFieldElements(xs: Field3[], f: bigint) { +function assertAlmostReduced(xs: Field3[], f: bigint) { let bounds: Field[] = []; for (let x of xs) { diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 2e9c4732f3..f722a39383 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -109,7 +109,7 @@ for (let F of fields) { equivalent({ from: [big264], to: unit })( (x) => assertWeakBound(x, F.modulus), - (x) => ForeignField.assertAlmostFieldElements([x], F.modulus) + (x) => ForeignField.assertAlmostReduced([x], F.modulus) ); // sumchain of 5 @@ -158,7 +158,7 @@ let ffProgram = ZkProgram({ mulWithBoundsCheck: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { - ForeignField.assertAlmostFieldElements([x, y], F.modulus); + ForeignField.assertAlmostReduced([x, y], F.modulus); return ForeignField.mul(x, y, F.modulus); }, }, diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 6f9f24b9a1..ce3a829a6a 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -372,7 +372,7 @@ const Gadgets = { /** * Foreign field subtraction: `x - y mod f` * - * See {@link ForeignField.add} for assumptions and usage examples. + * See {@link Gadgets.ForeignField.add} for assumptions and usage examples. * * @throws fails if `x - y < -f`, where the result cannot be brought back to a positive number by adding `f` once. */ @@ -390,7 +390,7 @@ const Gadgets = { * **Note**: For 3 or more inputs, `sum()` uses fewer constraints than a sequence of `add()` and `sub()` calls, * because we can avoid range checks on intermediate results. * - * See {@link ForeignField.add} for assumptions on inputs. + * See {@link Gadgets.ForeignField.add} for assumptions on inputs. * * @example * ```ts @@ -424,7 +424,7 @@ const Gadgets = { * To do this, we use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. * The implication is that x and y are _almost_ reduced modulo f. * - * All of the above assumptions are checked by {@link ForeignField.assertAlmostFieldElements}. + * All of the above assumptions are checked by {@link Gadgets.ForeignField.assertAlmostReduced}. * * **Warning**: This gadget does not add the extra bound check on the result. * So, to use the result in another foreign field multiplication, you have to add the bound check on it yourself, again. @@ -438,7 +438,7 @@ const Gadgets = { * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); * * // range check x, y and prove additional bounds x[2] <= f[2] - * ForeignField.assertAlmostFieldElements([x, y], f); + * ForeignField.assertAlmostReduced([x, y], f); * * // compute x * y mod f * let z = ForeignField.mul(x, y, f); @@ -453,7 +453,7 @@ const Gadgets = { /** * Foreign field inverse: `x^(-1) mod f` * - * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. * * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. */ @@ -464,11 +464,11 @@ const Gadgets = { /** * Foreign field division: `x * y^(-1) mod f` * - * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. * * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. * - * @throws Different than {@link ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. + * @throws Different than {@link Gadgets.ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. */ div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); @@ -477,13 +477,13 @@ const Gadgets = { /** * Optimized multiplication of sums in a foreign field, for example: `(x - y)*z = a + b + c mod f` * - * Note: This is much more efficient than using {@link ForeignField.add} and {@link ForeignField.sub} separately to - * compute the multiplication inputs and outputs, and then using {@link ForeignField.mul} to constrain the result. + * Note: This is much more efficient than using {@link Gadgets.ForeignField.add} and {@link Gadgets.ForeignField.sub} separately to + * compute the multiplication inputs and outputs, and then using {@link Gadgets.ForeignField.mul} to constrain the result. * - * The sums passed into this gadgets are "lazy sums" created with {@link ForeignField.Sum}. + * The sums passed into this gadgets are "lazy sums" created with {@link Gadgets.ForeignField.Sum}. * You can also pass in plain {@link Field3} elements. * - * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link ForeignField.mul}: + * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link Gadgets.ForeignField.mul}: * - each summand's limbs are in the range [0, 2^88) * - summands that are part of a multiplication input satisfy `x[2] <= f[2]` * @@ -495,7 +495,7 @@ const Gadgets = { * @example * ```ts * // range-check x, y, z, a, b, c - * ForeignField.assertAlmostFieldElements([x, y, z], f); + * ForeignField.assertAlmostReduced([x, y, z], f); * Gadgets.multiRangeCheck(a); * Gadgets.multiRangeCheck(b); * Gadgets.multiRangeCheck(c); @@ -513,7 +513,7 @@ const Gadgets = { }, /** - * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + * Lazy sum of {@link Field3} elements, which can be used as input to {@link Gadgets.ForeignField.assertMul}. */ Sum(x: Field3) { return ForeignField.Sum(x); @@ -521,7 +521,7 @@ const Gadgets = { /** * Prove that each of the given {@link Field3} elements is "almost" reduced modulo f, - * i.e., satisfies the assumptions required by {@link ForeignField.mul} and other gadgets: + * i.e., satisfies the assumptions required by {@link Gadgets.ForeignField.mul} and other gadgets: * - each limb is in the range [0, 2^88) * - the most significant limb is less or equal than the modulus, x[2] <= f[2] * @@ -535,18 +535,18 @@ const Gadgets = { * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); * - * ForeignField.assertAlmostFieldElements([x, y, z], f); + * ForeignField.assertAlmostReduced([x, y, z], f); * * // now we can use x, y, z as inputs to foreign field multiplication * let xy = ForeignField.mul(x, y, f); * let xyz = ForeignField.mul(xy, z, f); * * // since xy is an input to another multiplication, we need to prove that it is almost reduced again! - * ForeignField.assertAlmostFieldElements([xy], f); // TODO: would be more efficient to batch this with 2 other elements + * ForeignField.assertAlmostReduced([xy], f); // TODO: would be more efficient to batch this with 2 other elements * ``` */ - assertAlmostFieldElements(xs: Field3[], f: bigint) { - ForeignField.assertAlmostFieldElements(xs, f); + assertAlmostReduced(xs: Field3[], f: bigint) { + ForeignField.assertAlmostReduced(xs, f); }, }, @@ -566,7 +566,7 @@ const Gadgets = { * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); * * // assert that message hash and signature are valid scalar field elements - * Gadgets.ForeignField.assertAlmostFieldElements( + * Gadgets.ForeignField.assertAlmostReduced( * [signature.r, signature.s, msgHash], * Curve.order * ); @@ -620,7 +620,7 @@ export namespace Gadgets { export namespace ForeignField { /** - * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + * Lazy sum of {@link Field3} elements, which can be used as input to {@link Gadgets.ForeignField.assertMul}. */ export type Sum = Sum_; } From da21b199359c97aacba2b857dc29f2842f25d3ef Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:13:04 +0100 Subject: [PATCH 0966/1215] fixup merge --- src/lib/foreign-curve.ts | 3 ++- src/lib/foreign-field.ts | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index fa13a29d5c..84cff2edd0 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -96,7 +96,8 @@ class ForeignCurve { * Elliptic curve doubling. */ double() { - let p = EllipticCurve.double(toPoint(this), this.modulus); + let Curve = this.Constructor.Bigint; + let p = EllipticCurve.double(toPoint(this), Curve.modulus, Curve.a); return new this.Constructor(p); } diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 1524e8b495..46e4678e95 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -153,10 +153,8 @@ class ForeignField { // TODO: this is not very efficient, but the only way to abstract away the complicated // range check assumptions and also not introduce a global context of pending range checks. // we plan to get rid of bounds checks anyway, then this is just a multi-range check - Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, { - skipMrc: true, - }); - return this.Constructor.AlmostReduced.unsafeFrom(this); + let [x] = this.Constructor.assertAlmostReduced(this); + return x; } /** @@ -168,7 +166,7 @@ class ForeignField { static assertAlmostReduced>( ...xs: T ): TupleMap { - Gadgets.ForeignField.assertAlmostFieldElements( + Gadgets.ForeignField.assertAlmostReduced( xs.map((x) => x.value), this.modulus, { skipMrc: true } From 132d8796ef3c5b5800d7b9f4210ce70f038c81cb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:17:22 +0100 Subject: [PATCH 0967/1215] support a!=0 in assert on curve --- src/lib/foreign-curve.ts | 2 +- src/lib/gadgets/elliptic-curve.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 84cff2edd0..7010f57838 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -109,7 +109,7 @@ class ForeignCurve { } static assertOnCurve(g: ForeignCurve) { - EllipticCurve.assertOnCurve(toPoint(g), this.Bigint.modulus, this.Bigint.b); + EllipticCurve.assertOnCurve(toPoint(g), this.Bigint); } /** diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 0b9c985cb9..0f6e1cd7ee 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -147,16 +147,20 @@ function negate({ x, y }: Point, f: bigint) { return { x, y: ForeignField.negate(y, f) }; } -function assertOnCurve(p: Point, f: bigint, b: bigint) { - // TODO this assumes the curve has a == 0 +function assertOnCurve( + p: Point, + { modulus: f, a, b }: { modulus: bigint; b: bigint; a: bigint } +) { let { x, y } = p; let x2 = ForeignField.mul(x, x, f); let y2 = ForeignField.mul(y, y, f); let y2MinusB = ForeignField.Sum(y2).sub(Field3.from(b)); - // x^2 * x = y^2 - b + // (x^2 + a) * x = y^2 - b + let x2PlusA = ForeignField.Sum(x2); + if (a !== 0n) x2PlusA = x2PlusA.add(Field3.from(a)); let message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; - ForeignField.assertMul(x2, x, y2MinusB, f, message); + ForeignField.assertMul(x2PlusA, x, y2MinusB, f, message); } /** From d953a7061d2d5b0524587158f8b73c14cb41f818 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:36:29 +0100 Subject: [PATCH 0968/1215] implement subgroup check --- src/lib/foreign-curve.ts | 28 +++++----------------- src/lib/gadgets/elliptic-curve.ts | 40 ++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 7010f57838..a14d7ef724 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -125,34 +125,18 @@ class ForeignCurve { */ scale(scalar: AlmostForeignField | bigint | number) { let scalar_ = this.Constructor.Scalar.from(scalar); - if (this.isConstant() && scalar_.isConstant()) { - let z = this.Constructor.Bigint.scale( - this.toBigint(), - scalar_.toBigInt() - ); - return new this.Constructor(z); - } - let p = EllipticCurve.multiScalarMul( + let p = EllipticCurve.scale( this.Constructor.Bigint, - [scalar_.value], - [toPoint(this)], - [{ windowSize: 3 }] + scalar_.value, + toPoint(this) ); return new this.Constructor(p); } static assertInSubgroup(g: ForeignCurve) { - if (g.isConstant()) { - let isInGroup = this.Bigint.isInSubgroup(g.toBigint()); - if (!isInGroup) - throw Error( - `${this.name}.assertInSubgroup(): ${JSON.stringify( - this.provable.toJSON(g) - )} is not in the target subgroup.` - ); - return; + if (this.Bigint.hasCofactor) { + EllipticCurve.assertInSubgroup(this.Bigint, toPoint(g)); } - throw Error('unimplemented'); } /** @@ -173,7 +157,7 @@ class ForeignCurve { this.Field.check(g.x); this.Field.check(g.y); this.assertOnCurve(g); - if (this.Bigint.hasCofactor) this.assertInSubgroup(g); + this.assertInSubgroup(g); } // dynamic subclassing infra diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 0f6e1cd7ee..aad4909424 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -32,6 +32,7 @@ const EllipticCurve = { negate, assertOnCurve, scale, + assertInSubgroup, multiScalarMul, initialAggregator, }; @@ -172,27 +173,35 @@ function scale( Curve: CurveAffine, scalar: Field3, point: Point, - config: { windowSize?: number; multiples?: Point[] } = { windowSize: 3 } + config: { + mode?: 'assert-nonzero' | 'assert-zero'; + windowSize?: number; + multiples?: Point[]; + } = { mode: 'assert-nonzero' } ) { // constant case if (Field3.isConstant(scalar) && Point.isConstant(point)) { let scalar_ = Field3.toBigint(scalar); let p_ = Point.toBigint(point); let scaled = Curve.scale(p_, scalar_); + if (config.mode === 'assert-zero') { + assert(scaled.infinity, 'scale: expected zero result'); + return Point.from(Curve.zero); + } + assert(!scaled.infinity, 'scale: expected non-zero result'); return Point.from(scaled); } // provable case - return multiScalarMul(Curve, [scalar], [point], [config]); + config.windowSize ??= Point.isConstant(point) ? 4 : 3; + return multiScalarMul(Curve, [scalar], [point], [config], config.mode); } // checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 function assertInSubgroup(Curve: CurveAffine, p: Point) { const order = Field3.from(Curve.order); // [order]g = 0 - let orderG = scale(Curve, order, p); - // TODO support zero result from scale - throw Error('unimplemented'); + scale(Curve, order, p, { mode: 'assert-zero' }); } // check whether a point equals a constant point @@ -261,6 +270,7 @@ function verifyEcdsa( [u1, u2], [G, publicKey], config && [config.G, config.P], + 'assert-nonzero', config?.ia ); // this ^ already proves that R != 0 (part of ECDSA verification) @@ -280,9 +290,14 @@ function verifyEcdsa( * * s_0 * P_0 + ... + s_(n-1) * P_(n-1) * - * where P_i are any points. The result is not allowed to be zero. + * where P_i are any points. + * + * By default, we prove that the result is not zero. * - * We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. + * If you set the `mode` parameter to `'assert-zero'`, on the other hand, + * we assert that the result is zero and just return the constant zero point. + * + * Implementation: We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. * * Note: this algorithm targets a small number of points, like 2 needed for ECDSA verification. * @@ -298,6 +313,7 @@ function multiScalarMul( | { windowSize?: number; multiples?: Point[] } | undefined )[] = [], + mode: 'assert-nonzero' | 'assert-zero' = 'assert-nonzero', ia?: point ): Point { let n = points.length; @@ -362,9 +378,15 @@ function multiScalarMul( // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); let isZero = equals(sum, iaFinal, Curve.modulus); - isZero.assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + if (mode === 'assert-nonzero') { + isZero.assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + } else { + isZero.assertTrue(); + // for type consistency with the 'assert-nonzero' case + sum = Point.from(Curve.zero); + } return sum; } From d7707dcd13c153cb39f9a7ab111eeb42aeacbd45 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:39:08 +0100 Subject: [PATCH 0969/1215] fix assert on curve --- src/lib/gadgets/elliptic-curve.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index aad4909424..546a15e100 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -160,7 +160,10 @@ function assertOnCurve( // (x^2 + a) * x = y^2 - b let x2PlusA = ForeignField.Sum(x2); if (a !== 0n) x2PlusA = x2PlusA.add(Field3.from(a)); - let message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; + let message: string | undefined; + if (Point.isConstant(p)) { + message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; + } ForeignField.assertMul(x2PlusA, x, y2MinusB, f, message); } From ec7131ea46c91b166e3d15c55ffd2d68341094a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:42:27 +0100 Subject: [PATCH 0970/1215] not so slow anymore --- src/lib/foreign-curve.unit-test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 425679ebfb..1d2bb19534 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -20,11 +20,9 @@ function main() { Provable.assertEqual(Vesta.provable, h0, new Vesta(h)); h0.assertOnCurve(); - // TODO super slow - // h0.assertInSubgroup(); + h0.assertInSubgroup(); let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); - // TODO super slow let p0 = h0.scale(scalar0); Provable.assertEqual(Vesta.provable, p0, new Vesta(p)); } @@ -38,10 +36,11 @@ Provable.runAndCheck(main); console.timeEnd('running witness generation & checks'); console.time('creating constraint system'); -let { gates } = Provable.constraintSystem(main); +let { gates, rows } = Provable.constraintSystem(main); console.timeEnd('creating constraint system'); let gateTypes: Record = {}; +gateTypes['Total rows'] = rows; for (let gate of gates) { gateTypes[gate.type] ??= 0; gateTypes[gate.type]++; From 1f7e291f0ee560fb1e1df8cb0ff64b046b11219f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:53:14 +0100 Subject: [PATCH 0971/1215] expose cs summary --- src/examples/benchmarks/foreign-field.ts | 21 ++++++++------------- src/examples/zkprogram/ecdsa/run.ts | 8 +------- src/lib/foreign-curve.unit-test.ts | 11 ++--------- src/lib/foreign-ecdsa.unit-test.ts | 9 +-------- src/lib/gadgets/ecdsa.unit-test.ts | 9 +-------- src/lib/provable-context.ts | 11 ++++++++++- 6 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts index c785a32d90..cff76159a8 100644 --- a/src/examples/benchmarks/foreign-field.ts +++ b/src/examples/benchmarks/foreign-field.ts @@ -1,6 +1,8 @@ -import { Scalar, vestaParams, Provable, createForeignField } from 'snarkyjs'; +import { Scalar, Crypto, Provable, createForeignField } from 'o1js'; -class ForeignScalar extends createForeignField(vestaParams.modulus) {} +class ForeignScalar extends createForeignField( + Crypto.CurveParams.Secp256k1.modulus +).AlmostReduced {} // TODO ForeignField.random() function random() { @@ -8,8 +10,8 @@ function random() { } function main() { - let s = Provable.witness(ForeignScalar, random); - let t = Provable.witness(ForeignScalar, random); + let s = Provable.witness(ForeignScalar.provable, random); + let t = Provable.witness(ForeignScalar.provable, random); s.mul(t); } @@ -17,19 +19,12 @@ console.time('running constant version'); main(); console.timeEnd('running constant version'); -// half of this time is spent in `field_to_bignum_bigint`, which is mostly addition of zarith bigints -.- console.time('running witness generation & checks'); Provable.runAndCheck(main); console.timeEnd('running witness generation & checks'); console.time('creating constraint system'); -let { gates } = Provable.constraintSystem(main); +let cs = Provable.constraintSystem(main); console.timeEnd('creating constraint system'); -let gateTypes: Record = {}; -for (let gate of gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); +console.log(cs.summary()); diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts index b1d502c7e9..a1c9618d79 100644 --- a/src/examples/zkprogram/ecdsa/run.ts +++ b/src/examples/zkprogram/ecdsa/run.ts @@ -18,13 +18,7 @@ console.time('ecdsa verify (build constraint system)'); let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; console.timeEnd('ecdsa verify (build constraint system)'); -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} -console.log(gateTypes); +console.log(cs.summary()); // compile and prove diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts index 1d2bb19534..9cdac03730 100644 --- a/src/lib/foreign-curve.unit-test.ts +++ b/src/lib/foreign-curve.unit-test.ts @@ -36,14 +36,7 @@ Provable.runAndCheck(main); console.timeEnd('running witness generation & checks'); console.time('creating constraint system'); -let { gates, rows } = Provable.constraintSystem(main); +let cs = Provable.constraintSystem(main); console.timeEnd('creating constraint system'); -let gateTypes: Record = {}; -gateTypes['Total rows'] = rows; -for (let gate of gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); +console.log(cs.summary()); diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/lib/foreign-ecdsa.unit-test.ts index afd1a06a6a..afd60415d3 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/lib/foreign-ecdsa.unit-test.ts @@ -38,11 +38,4 @@ console.time('ecdsa verify (build constraint system)'); let cs = Provable.constraintSystem(main); console.timeEnd('ecdsa verify (build constraint system)'); -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); +console.log(cs.summary()); diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 33c1f8ce6f..d2b9e5b836 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -137,14 +137,7 @@ console.time('ecdsa verify (build constraint system)'); let cs = program.analyzeMethods().ecdsa; console.timeEnd('ecdsa verify (build constraint system)'); -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); +console.log(cs.summary()); console.time('ecdsa verify (compile)'); await program.compile(); diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 0ce0030556..3b79562889 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -1,5 +1,5 @@ import { Context } from './global-context.js'; -import { Gate, JsonGate, Snarky } from '../snarky.js'; +import { Gate, GateType, JsonGate, Snarky } from '../snarky.js'; import { parseHexString } from '../bindings/crypto/bigint-helpers.js'; import { prettifyStacktrace } from './errors.js'; import { Fp } from '../bindings/crypto/finite_field.js'; @@ -105,6 +105,15 @@ function constraintSystem(f: () => T) { print() { printGates(gates); }, + summary() { + let gateTypes: Partial> = {}; + gateTypes['Total rows'] = rows; + for (let gate of gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]!++; + } + return gateTypes; + }, }; } catch (error) { throw prettifyStacktrace(error); From 0122785e99c2c6db3b7e2b956cf94724068e3ab8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:53:22 +0100 Subject: [PATCH 0972/1215] delete bad example --- src/examples/benchmarks/foreign-curve.ts | 46 ------------------------ 1 file changed, 46 deletions(-) delete mode 100644 src/examples/benchmarks/foreign-curve.ts diff --git a/src/examples/benchmarks/foreign-curve.ts b/src/examples/benchmarks/foreign-curve.ts deleted file mode 100644 index 4c8178b7e3..0000000000 --- a/src/examples/benchmarks/foreign-curve.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createForeignCurve, vestaParams, Provable, Field } from 'snarkyjs'; - -class Vesta extends createForeignCurve(vestaParams) {} - -let g = new Vesta({ x: -1n, y: 2n }); -let scalar = Field.random(); -let h = g.add(Vesta.generator).double().negate(); -let p = h.scale(scalar.toBits()); - -function main() { - Vesta.initialize(); - let g0 = Provable.witness(Vesta, () => g); - let one = Provable.witness(Vesta, () => Vesta.generator); - let h0 = g0.add(one).double().negate(); - Provable.assertEqual(Vesta, h0, new Vesta(h)); - - h0.assertOnCurve(); - // TODO super slow - // h0.assertInSubgroup(); - - let scalar0 = Provable.witness(Field, () => scalar).toBits(); - // TODO super slow - let p0 = h0.scale(scalar0); - Provable.assertEqual(Vesta, p0, p); -} - -console.time('running constant version'); -main(); -console.timeEnd('running constant version'); - -// half of this time is spent in `field_to_bignum_bigint`, which is mostly addition of zarith bigints -.- -console.time('running witness generation & checks'); -Provable.runAndCheck(main); -console.timeEnd('running witness generation & checks'); - -console.time('creating constraint system'); -let { gates } = Provable.constraintSystem(main); -console.timeEnd('creating constraint system'); - -let gateTypes: Record = {}; -for (let gate of gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); From 4b5af0b90f114dfb447cb067896ee9717d798504 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:59:24 +0100 Subject: [PATCH 0973/1215] remove curve parameter limitations from foreign curve class --- src/lib/foreign-curve.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index a14d7ef724..64e4bbecdb 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -229,8 +229,6 @@ function createForeignCurve(params: CurveParams): typeof ForeignCurve { class Scalar extends ScalarUnreduced.AlmostReduced {} const BigintCurve = createCurveAffine(params); - assert(BigintCurve.a === 0n, 'a !=0 is not supported'); - assert(!BigintCurve.hasCofactor, 'cofactor != 1 is not supported'); class Curve extends ForeignCurve { static _Bigint = BigintCurve; From 0ea7a425dda75605fa1463d57f5aea1ffa1b05d8 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Mon, 4 Dec 2023 13:12:10 +0000 Subject: [PATCH 0974/1215] Add comment to make piRoh more clear (hopefully) --- src/lib/keccak.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 1187d89b78..dc01bebc2d 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -107,7 +107,9 @@ const theta = (state: Field[][]): Field[][] => { }; // Second and third steps in the compression step of Keccak for 64-bit words. -// B[y,2x+3y] = ROT(E[x,y], r[x,y]) +// pi: A[x,y] = ROT(E[x,y], r[x,y]) +// rho: A[x,y] = A'[y, 2x+3y mod KECCAK_DIM] +// piRho: B[y,2x+3y] = ROT(E[x,y], r[x,y]) // which is equivalent to the `rho` algorithm followed by the `pi` algorithm in the Keccak reference as follows: // rho: // A[0,0] = a[0,0] @@ -212,4 +214,13 @@ function permutation(state: Field[][], rc: Field[]): Field[][] { const blockTransformation = (state: Field[][]): Field[][] => permutation(state, ROUND_CONSTANTS); -export { KECCAK_DIM, ROUND_CONSTANTS, theta, piRho, chi, iota, round, blockTransformation }; +export { + KECCAK_DIM, + ROUND_CONSTANTS, + theta, + piRho, + chi, + iota, + round, + blockTransformation, +}; From e1282d18e429be50d0f6fb3543ff68408a579a9c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 14:15:45 +0100 Subject: [PATCH 0975/1215] cleanup comments, move redundant unit test to example --- src/examples/crypto/README.md | 1 + .../crypto/ecdsa.ts} | 5 +---- src/lib/foreign-curve.ts | 19 ++++++------------- 3 files changed, 8 insertions(+), 17 deletions(-) rename src/{lib/foreign-ecdsa.unit-test.ts => examples/crypto/ecdsa.ts} (86%) diff --git a/src/examples/crypto/README.md b/src/examples/crypto/README.md index 60d0d50d51..c2f913defa 100644 --- a/src/examples/crypto/README.md +++ b/src/examples/crypto/README.md @@ -3,3 +3,4 @@ These examples show how to use some of the crypto primitives that are supported in provable o1js code. - Non-native field arithmetic: `foreign-field.ts` +- Non-native ECDSA verification: `ecdsa.ts` diff --git a/src/lib/foreign-ecdsa.unit-test.ts b/src/examples/crypto/ecdsa.ts similarity index 86% rename from src/lib/foreign-ecdsa.unit-test.ts rename to src/examples/crypto/ecdsa.ts index afd60415d3..eb94a06ba5 100644 --- a/src/lib/foreign-ecdsa.unit-test.ts +++ b/src/examples/crypto/ecdsa.ts @@ -1,7 +1,4 @@ -import { createEcdsa } from './foreign-ecdsa.js'; -import { createForeignCurve } from './foreign-curve.js'; -import { Provable } from './provable.js'; -import { Crypto } from './crypto.js'; +import { Crypto, createForeignCurve, createEcdsa, Provable } from 'o1js'; class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 64e4bbecdb..c15a8a9cce 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -43,7 +43,10 @@ class ForeignCurve { this.x = new this.Constructor.Field(g.x); this.y = new this.Constructor.Field(g.y); // don't allow constants that aren't on the curve - if (this.isConstant()) this.assertOnCurve(); + if (this.isConstant()) { + this.assertOnCurve(); + this.assertInSubgroup(); + } } /** @@ -203,24 +206,14 @@ class ForeignCurve { * Create a class representing an elliptic curve group, which is different from the native {@link Group}. * * ```ts - * const Curve = createForeignCurve(secp256k1Params); // the elliptic curve 'secp256k1' + * const Curve = createForeignCurve(Crypto.CurveParams.Secp256k1); * ``` * * `createForeignCurve(params)` takes the curve parameters {@link CurveParams} as input. * We support `modulus` and `order` to be prime numbers to 259 bits. * * The returned {@link ForeignCurve} class supports standard elliptic curve operations like point addition and scalar multiplication. - * It also includes to associated foreign fields: `ForeignCurve.BaseField` and `ForeignCurve.Scalar`, see {@link createForeignField}. - * - * _Advanced usage:_ - * - * To skip automatic validity checks when introducing curve points and scalars into provable code, - * use the optional `{ unsafe: true }` configuration. See {@link createForeignField} for details. - * This option is applied to both the scalar field and the base field. - * - * @param params parameters for the elliptic curve you are instantiating - * @param options - * - `unsafe: boolean` determines whether `ForeignField` elements are constrained to be valid on creation. + * It also includes to associated foreign fields: `ForeignCurve.Field` and `ForeignCurve.Scalar`, see {@link createForeignField}. */ function createForeignCurve(params: CurveParams): typeof ForeignCurve { const FieldUnreduced = createForeignField(params.modulus); From 1348779edfcad9e9ec5cedeb8353318b27fb4533 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 15:25:04 +0100 Subject: [PATCH 0976/1215] examples fixup --- src/examples/zkprogram/ecdsa/ecdsa.ts | 2 +- src/lib/gadgets/gadgets.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts index 183ec3e7f1..810cb33e55 100644 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ b/src/examples/zkprogram/ecdsa/ecdsa.ts @@ -28,7 +28,7 @@ const ecdsaProgram = ZkProgram({ msgHash: Gadgets.Field3 ) { // assert that private inputs are valid - ForeignField.assertAlmostFieldElements( + ForeignField.assertAlmostReduced( [signature.r, signature.s, msgHash], Secp256k1.order ); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f6ca32bef4..f229d7923e 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -575,11 +575,11 @@ const Gadgets = { * Prove that x < f for any constant f < 2^264. * * 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.assertAlmostFieldElements} + * This is a stronger statement than {@link ForeignField.assertAlmostReduced} * and also uses more constraints; it should not be needed in most use cases. * * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to - * {@link ForeignField.assertAlmostFieldElements} which adds that check itself. + * {@link ForeignField.assertAlmostReduced} which adds that check itself. * * @throws if x is greater or equal to f. * @@ -631,7 +631,7 @@ const Gadgets = { msgHash: Field3, publicKey: Point ) { - Ecdsa.verify(Curve, signature, msgHash, publicKey); + return Ecdsa.verify(Curve, signature, msgHash, publicKey); }, /** From 61008171bf97b91a668fd302c81589cd1988870a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 15:25:21 +0100 Subject: [PATCH 0977/1215] make ecdsa class work --- src/lib/foreign-curve.ts | 3 ++ src/lib/foreign-ecdsa.ts | 77 ++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 50 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index c15a8a9cce..bb5e962bd2 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -15,6 +15,9 @@ import { provableFromClass } from '../bindings/lib/provable-snarky.js'; // external API export { createForeignCurve, ForeignCurve }; +// internal API +export { toPoint }; + type FlexiblePoint = { x: AlmostForeignField | Field3 | bigint | number; y: AlmostForeignField | Field3 | bigint | number; diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 5c4dba5061..3128e88efc 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,9 +1,9 @@ import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; import { Struct } from './circuit_value.js'; -import { ForeignCurve, createForeignCurve } from './foreign-curve.js'; +import { ForeignCurve, createForeignCurve, toPoint } from './foreign-curve.js'; import { AlmostForeignField } from './foreign-field.js'; -import { verifyEcdsaConstant } from './gadgets/elliptic-curve.js'; -import { Provable } from './provable.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { Gadgets } from './gadgets/gadgets.js'; // external API export { createEcdsa }; @@ -29,18 +29,22 @@ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { class EcdsaSignature extends Signature { static Curve = Curve0; + constructor(signature: { + r: Scalar | Field3 | bigint; + s: Scalar | Field3 | bigint; + }) { + super({ r: new Scalar(signature.r), s: new Scalar(signature.s) }); + } + /** * Coerce the input to a {@link EcdsaSignature}. */ static from(signature: { - r: Scalar | bigint; - s: Scalar | bigint; + r: Scalar | Field3 | bigint; + s: Scalar | Field3 | bigint; }): EcdsaSignature { - if (signature instanceof EcdsaSignature) return signature; - return new EcdsaSignature({ - r: Scalar.from(signature.r), - s: Scalar.from(signature.s), - }); + if (signature instanceof this) return signature; + return new EcdsaSignature(signature); } /** @@ -48,16 +52,8 @@ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). */ static fromHex(rawSignature: string): EcdsaSignature { - let prefix = rawSignature.slice(0, 2); - let signature = rawSignature.slice(2, 130); - if (prefix !== '0x' || signature.length < 128) { - throw Error( - `${this.name}.fromHex(): Invalid signature, expected hex string 0x... of length at least 130.` - ); - } - let r = BigInt(`0x${signature.slice(0, 64)}`); - let s = BigInt(`0x${signature.slice(64)}`); - return new EcdsaSignature({ r: Scalar.from(r), s: Scalar.from(s) }); + let s = Gadgets.Ecdsa.Signature.fromHex(rawSignature); + return new EcdsaSignature(s); } /** @@ -68,38 +64,15 @@ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { verify( msgHash: Scalar | bigint, publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } - ): void { + ) { let msgHash_ = Scalar.from(msgHash); let publicKey_ = Curve.from(publicKey); - - if (Provable.isConstant(Signature, this)) { - let isValid = verifyEcdsaConstant( - Curve.Bigint, - { r: this.r.toBigInt(), s: this.s.toBigInt() }, - msgHash_.toBigInt(), - publicKey_.toBigint() - ); - if (!isValid) { - throw Error(`${this.constructor.name}.verify(): Invalid signature.`); - } - return; - } - throw Error('unimplemented'); - } - - /** - * Check that r, s are valid scalars and both are non-zero - */ - static check(signature: { r: Scalar; s: Scalar }) { - if (Provable.isConstant(Signature, signature)) { - super.check(signature); // check valid scalars - if (signature.r.toBigInt() === 0n) - throw Error(`${this.name}.check(): r must be non-zero`); - if (signature.s.toBigInt() === 0n) - throw Error(`${this.name}.check(): s must be non-zero`); - return; - } - throw Error('unimplemented'); + return Gadgets.Ecdsa.verify( + Curve.Bigint, + toObject(this), + msgHash_.value, + toPoint(publicKey_) + ); } static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); @@ -107,3 +80,7 @@ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { return EcdsaSignature; } + +function toObject(signature: Signature) { + return { r: signature.r.value, s: signature.s.value }; +} From ecdeea72bb06fb9ba99c934b7ae6a3fce4420a99 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 15:51:31 +0100 Subject: [PATCH 0978/1215] move ecdsa class outside factory --- src/examples/crypto/ecdsa.ts | 2 +- src/lib/foreign-curve.ts | 2 +- src/lib/foreign-ecdsa.ts | 157 +++++++++++++++++++++-------------- 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/src/examples/crypto/ecdsa.ts b/src/examples/crypto/ecdsa.ts index eb94a06ba5..276c6b7dec 100644 --- a/src/examples/crypto/ecdsa.ts +++ b/src/examples/crypto/ecdsa.ts @@ -19,7 +19,7 @@ let msgHash = ); function main() { - let signature0 = Provable.witness(EthSignature, () => signature); + let signature0 = Provable.witness(EthSignature.provable, () => signature); signature0.verify(msgHash, publicKey); } diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index bb5e962bd2..4b0aac1cc2 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -16,7 +16,7 @@ import { provableFromClass } from '../bindings/lib/provable-snarky.js'; export { createForeignCurve, ForeignCurve }; // internal API -export { toPoint }; +export { toPoint, FlexiblePoint }; type FlexiblePoint = { x: AlmostForeignField | Field3 | bigint | number; diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 3128e88efc..6d35c81e39 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,14 +1,98 @@ +import { provableFromClass } from '../bindings/lib/provable-snarky.js'; import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; -import { Struct } from './circuit_value.js'; -import { ForeignCurve, createForeignCurve, toPoint } from './foreign-curve.js'; +import { ProvableExtended } from './circuit_value.js'; +import { + FlexiblePoint, + ForeignCurve, + createForeignCurve, + toPoint, +} from './foreign-curve.js'; import { AlmostForeignField } from './foreign-field.js'; +import { assert } from './gadgets/common.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; // external API export { createEcdsa }; -type Signature = { r: AlmostForeignField; s: AlmostForeignField }; +type FlexibleSignature = + | EcdsaSignature + | { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }; + +class EcdsaSignature { + r: AlmostForeignField; + s: AlmostForeignField; + + /** + * Create a new {@link EcdsaSignature} from an object containing the scalars r and s. + * @param signature + */ + constructor(signature: { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }) { + this.r = new this.Constructor.Curve.Scalar(signature.r); + this.s = new this.Constructor.Curve.Scalar(signature.s); + } + + /** + * Coerce the input to a {@link EcdsaSignature}. + */ + static from(signature: FlexibleSignature): EcdsaSignature { + if (signature instanceof this) return signature; + return new this(signature); + } + + /** + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ + static fromHex(rawSignature: string): EcdsaSignature { + let s = Gadgets.Ecdsa.Signature.fromHex(rawSignature); + return new this(s); + } + + /** + * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * + * This method proves that the signature is valid, and throws if it isn't. + */ + verify(msgHash: AlmostForeignField | bigint, publicKey: FlexiblePoint) { + let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); + let publicKey_ = this.Constructor.Curve.from(publicKey); + return Gadgets.Ecdsa.verify( + this.Constructor.Curve.Bigint, + toObject(this), + msgHash_.value, + toPoint(publicKey_) + ); + } + + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof EcdsaSignature; + } + static _Curve?: typeof ForeignCurve; + static _provable?: ProvableExtended; + + /** + * Curve arithmetic on JS bigints. + */ + static get Curve() { + assert(this._Curve !== undefined, 'EcdsaSignature not initialized'); + return this._Curve; + } + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'EcdsaSignature not initialized'); + return this._provable; + } +} /** * Returns a class {@link EcdsaSignature} enabling to parse and verify ECDSA signature in provable code, @@ -18,69 +102,18 @@ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { let Curve0: typeof ForeignCurve = 'b' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} - class Scalar extends Curve.Scalar {} - class BaseField extends Curve.Field {} - - const Signature: Struct = Struct({ - r: Scalar.provable, - s: Scalar.provable, - }); - - class EcdsaSignature extends Signature { - static Curve = Curve0; - - constructor(signature: { - r: Scalar | Field3 | bigint; - s: Scalar | Field3 | bigint; - }) { - super({ r: new Scalar(signature.r), s: new Scalar(signature.s) }); - } - - /** - * Coerce the input to a {@link EcdsaSignature}. - */ - static from(signature: { - r: Scalar | Field3 | bigint; - s: Scalar | Field3 | bigint; - }): EcdsaSignature { - if (signature instanceof this) return signature; - return new EcdsaSignature(signature); - } - - /** - * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in - * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). - */ - static fromHex(rawSignature: string): EcdsaSignature { - let s = Gadgets.Ecdsa.Signature.fromHex(rawSignature); - return new EcdsaSignature(s); - } - - /** - * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). - * - * This method proves that the signature is valid, and throws if it isn't. - */ - verify( - msgHash: Scalar | bigint, - publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } - ) { - let msgHash_ = Scalar.from(msgHash); - let publicKey_ = Curve.from(publicKey); - return Gadgets.Ecdsa.verify( - Curve.Bigint, - toObject(this), - msgHash_.value, - toPoint(publicKey_) - ); - } - static dummy = new EcdsaSignature({ r: new Scalar(1), s: new Scalar(1) }); + class Signature extends EcdsaSignature { + static _Curve = Curve; + static _provable = provableFromClass(Signature, { + r: Curve.Scalar.provable, + s: Curve.Scalar.provable, + }); } - return EcdsaSignature; + return Signature; } -function toObject(signature: Signature) { +function toObject(signature: EcdsaSignature) { return { r: signature.r.value, s: signature.s.value }; } From 9fe6e5955d8829536cdf79b3b8d7af3196087206 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:00:23 +0100 Subject: [PATCH 0979/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1b0258a3b0..6c3e61f002 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1b0258a3b0e150a3b9a15b61c56115ef088a3865 +Subproject commit 6c3e61f002cf9c5dd3aa9374c3162a98786b3880 From 62902022a04f820773fe848b443d89fc79527c97 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:07:06 +0100 Subject: [PATCH 0980/1215] make type pure --- src/lib/foreign-curve.ts | 7 +++++-- src/lib/foreign-ecdsa.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 4b0aac1cc2..7367024a75 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -4,7 +4,7 @@ import { createCurveAffine, } from '../bindings/crypto/elliptic_curve.js'; import type { Group } from './group.js'; -import { ProvableExtended } from './circuit_value.js'; +import { ProvablePureExtended } from './circuit_value.js'; import { AlmostForeignField, createForeignField } from './foreign-field.js'; import { EllipticCurve, Point } from './gadgets/elliptic-curve.js'; import { Field3 } from './gadgets/foreign-field.js'; @@ -173,7 +173,10 @@ class ForeignCurve { static _Bigint?: CurveAffine; static _Field?: typeof AlmostForeignField; static _Scalar?: typeof AlmostForeignField; - static _provable?: ProvableExtended; + static _provable?: ProvablePureExtended< + ForeignCurve, + { x: string; y: string } + >; /** * Curve arithmetic on JS bigints. diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 6d35c81e39..a3cb80ad65 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,6 +1,6 @@ import { provableFromClass } from '../bindings/lib/provable-snarky.js'; import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; -import { ProvableExtended } from './circuit_value.js'; +import { ProvablePureExtended } from './circuit_value.js'; import { FlexiblePoint, ForeignCurve, @@ -76,7 +76,10 @@ class EcdsaSignature { return this.constructor as typeof EcdsaSignature; } static _Curve?: typeof ForeignCurve; - static _provable?: ProvableExtended; + static _provable?: ProvablePureExtended< + EcdsaSignature, + { r: string; s: string } + >; /** * Curve arithmetic on JS bigints. From 9dea77f3c1ac2da764c532a6bd2b4dc1124fdee3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:18:41 +0100 Subject: [PATCH 0981/1215] add ForeignField.random() --- src/lib/foreign-field.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 46e4678e95..662e575e3c 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -1,4 +1,9 @@ -import { mod, Fp } from '../bindings/crypto/finite_field.js'; +import { + mod, + Fp, + FiniteField, + createField, +} from '../bindings/crypto/finite_field.js'; import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; @@ -19,9 +24,14 @@ export type { }; class ForeignField { + static _Bigint: FiniteField | undefined = undefined; static _modulus: bigint | undefined = undefined; // static parameters + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignField class not initialized.'); + return this._Bigint; + } static get modulus() { assert(this._modulus !== undefined, 'ForeignField class not initialized.'); return this._modulus; @@ -389,6 +399,10 @@ class ForeignField { return new this.AlmostReduced([l0, l1, l2]); } + static random() { + return new this.Canonical(this.Bigint.random()); + } + /** * Instance version of `Provable.toFields`, see {@link Provable.toFields} */ @@ -607,7 +621,10 @@ function createForeignField(modulus: bigint): typeof UnreducedForeignField { `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` ); + let Bigint = createField(modulus); + class UnreducedField extends UnreducedForeignField { + static _Bigint = Bigint; static _modulus = modulus; static _provable = provable(UnreducedField); @@ -618,6 +635,7 @@ function createForeignField(modulus: bigint): typeof UnreducedForeignField { } class AlmostField extends AlmostForeignField { + static _Bigint = Bigint; static _modulus = modulus; static _provable = provable(AlmostField); @@ -629,6 +647,7 @@ function createForeignField(modulus: bigint): typeof UnreducedForeignField { } class CanonicalField extends CanonicalForeignField { + static _Bigint = Bigint; static _modulus = modulus; static _provable = provable(CanonicalField); From a3577b01a94ec66c561793b99af2b3b4db259aba Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:18:53 +0100 Subject: [PATCH 0982/1215] add Ecdsa.sign() --- src/lib/foreign-ecdsa.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index a3cb80ad65..1d5ab5ffc9 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -71,6 +71,14 @@ class EcdsaSignature { ); } + /** + * Create an {@link EcdsaSignature} by signing a message hash with a private key. + */ + static sign(msgHash: bigint, privateKey: bigint) { + let { r, s } = Gadgets.Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); + return new this({ r, s }); + } + // dynamic subclassing infra get Constructor() { return this.constructor as typeof EcdsaSignature; From b35cc616019f1fdbb365c70c423006d1ab4f737c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:20:21 +0100 Subject: [PATCH 0983/1215] rewrite ecdsa example using high level API --- src/examples/zkprogram/ecdsa/ecdsa.ts | 39 +++++++-------------------- src/examples/zkprogram/ecdsa/run.ts | 14 ++++------ 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts index 810cb33e55..8268117033 100644 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ b/src/examples/zkprogram/ecdsa/ecdsa.ts @@ -1,40 +1,21 @@ -import { Gadgets, ZkProgram, Struct, Crypto } from 'o1js'; +import { ZkProgram, Crypto, createEcdsa, createForeignCurve, Bool } from 'o1js'; -export { ecdsaProgram, Point, Secp256k1 }; +export { ecdsaProgram, Secp256k1, Ecdsa }; -let { ForeignField, Field3, Ecdsa } = Gadgets; - -// TODO expose this as part of Gadgets.Curve - -class Point extends Struct({ x: Field3.provable, y: Field3.provable }) { - // point from bigints - static from({ x, y }: { x: bigint; y: bigint }) { - return new Point({ x: Field3.from(x), y: Field3.from(y) }); - } -} - -const Secp256k1 = Crypto.createCurve(Crypto.CurveParams.Secp256k1); +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Scalar extends Secp256k1.Scalar {} +class Ecdsa extends createEcdsa(Secp256k1) {} const ecdsaProgram = ZkProgram({ name: 'ecdsa', - publicInput: Point, + publicInput: Scalar.provable, + publicOutput: Bool, methods: { verifyEcdsa: { - privateInputs: [Ecdsa.Signature.provable, Field3.provable], - method( - publicKey: Point, - signature: Gadgets.Ecdsa.Signature, - msgHash: Gadgets.Field3 - ) { - // assert that private inputs are valid - ForeignField.assertAlmostReduced( - [signature.r, signature.s, msgHash], - Secp256k1.order - ); - - // verify signature - Ecdsa.verify(Secp256k1, signature, msgHash, publicKey); + privateInputs: [Ecdsa.provable, Secp256k1.provable], + method(msgHash: Scalar, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verify(msgHash, publicKey); }, }, }, diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts index a1c9618d79..a2e49f7062 100644 --- a/src/examples/zkprogram/ecdsa/run.ts +++ b/src/examples/zkprogram/ecdsa/run.ts @@ -1,16 +1,15 @@ -import { Gadgets } from 'o1js'; -import { Point, Secp256k1, ecdsaProgram } from './ecdsa.js'; +import { Secp256k1, Ecdsa, ecdsaProgram } from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature let privateKey = Secp256k1.Scalar.random(); -let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); +let publicKey = Secp256k1.generator.scale(privateKey); // TODO use an actual keccak hash let messageHash = Secp256k1.Scalar.random(); -let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); +let signature = Ecdsa.sign(messageHash.toBigInt(), privateKey.toBigInt()); // investigate the constraint system generated by ECDSA verify @@ -27,11 +26,8 @@ await ecdsaProgram.compile(); console.timeEnd('ecdsa verify (compile)'); console.time('ecdsa verify (prove)'); -let proof = await ecdsaProgram.verifyEcdsa( - Point.from(publicKey), - Gadgets.Ecdsa.Signature.from(signature), - Gadgets.Field3.from(messageHash) -); +let proof = await ecdsaProgram.verifyEcdsa(messageHash, signature, publicKey); console.timeEnd('ecdsa verify (prove)'); +proof.publicOutput.assertTrue('signature verifies'); assert(await ecdsaProgram.verify(proof), 'proof verifies'); From 5923fb08f7995e8159b1172949e9ea4d0dbdfda1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:26:30 +0100 Subject: [PATCH 0984/1215] fix doccomment --- src/lib/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 06304095a7..d5ef7447b6 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -552,7 +552,7 @@ function isConstant(x: bigint | number | string | ForeignField) { * const SmallField = createForeignField(17n); // the finite field F_17 * ``` * - * `createForeignField(p)` takes {@link AlmostForeignField} the prime modulus `p` of the finite field as input, as a bigint. + * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. * We support prime moduli up to a size of 259 bits. * * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), From 6fc4e86f0dadc84af6cb87cef57edc2695e1b0db Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:39:19 +0100 Subject: [PATCH 0985/1215] fixup vk test --- src/examples/zkprogram/ecdsa/ecdsa.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts index 183ec3e7f1..810cb33e55 100644 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ b/src/examples/zkprogram/ecdsa/ecdsa.ts @@ -28,7 +28,7 @@ const ecdsaProgram = ZkProgram({ msgHash: Gadgets.Field3 ) { // assert that private inputs are valid - ForeignField.assertAlmostFieldElements( + ForeignField.assertAlmostReduced( [signature.r, signature.s, msgHash], Secp256k1.order ); From 5095fac473537f6c3f9292d84b92b5ad6c945347 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:51:18 +0100 Subject: [PATCH 0986/1215] fix constraint system test flakiness --- src/lib/testing/constraint-system.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 2922afa11d..1f8ad9ba52 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -389,7 +389,7 @@ function drawFieldVar(): FieldVar { let fieldType = drawFieldType(); switch (fieldType) { case FieldType.Constant: { - return FieldVar.constant(17n); + return FieldVar.constant(1n); } case FieldType.Var: { return [FieldType.Var, 0]; @@ -397,10 +397,14 @@ function drawFieldVar(): FieldVar { case FieldType.Add: { let x = drawFieldVar(); let y = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant && y[0] === FieldType.Constant) return x; return FieldVar.add(x, y); } case FieldType.Scale: { let x = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant) return x; return FieldVar.scale(3n, x); } } From 3e2990cf7c8d046a3c2e8d88e9eec62a2c473c17 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 17:01:37 +0100 Subject: [PATCH 0987/1215] collateral damage from using higher-level API --- tests/vk-regression/vk-regression.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 0297b30a04..7d90f28524 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "6e4078c6df944db119dc1656eb68e6c272c3f2e9cab6746913759537cbfcfa9", + "digest": "f89c0d140264e085d085f52999fc67d8e3db3909627d8e3e1a087c319853933", "methods": { "verifyEcdsa": { - "rows": 38846, - "digest": "892b0a1fad0f13d92ba6099cd54e6780" + "rows": 38912, + "digest": "5729fe17ff1af33e643a2f7e08292232" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJy9KmK2C+3hCZoGpDDhYsWLlly6udS3Uh6qYr0X1NU/Ns8UpTCq9fR7ST8OmK+DdktHHPQGmGD2FHfSOPBhRicgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AggZ9tT5WqF5mKwaoycspge8k5SYrr9T1DwdfhQHP86zGDZzvZlQGyPIvrcZ+bpTMc5+4GUl8mI5/IIZnX0cM0HV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "21152043351405736424630704375244880683906889913335338686836578165782029001213" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJ5BebLL3mIFBjZTEd6aY0P+vdDIg7SU4di7OM61CjQErcEn5Dyn9twBE9Rx7HN+Rix7+FJy63pyr43bG0OoID4gKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgvnOM9xbrxf3uNXY+sxBV1C0Y1XMbxUBeg2319X1diC8anDkLuiR5M6RL9jqRVurwp8dpL1mlssvdUMEAWVbRGV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "692488770088381683600018326540220594385299569804743037992171368071788655058" } } } \ No newline at end of file From b84edbbd5f1febb8fe9c742ccaa281f7b00fd342 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:03:00 +0000 Subject: [PATCH 0988/1215] Implement Keccak sponge --- src/lib/keccak.ts | 385 ++++++++++++++++++++++++++++++++++-- src/lib/keccak.unit-test.ts | 133 +++++-------- 2 files changed, 416 insertions(+), 102 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index dc01bebc2d..d01f0cfd97 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,5 +1,11 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; +import { assert } from './errors.js'; +import { existsOne, exists } from './gadgets/common.js'; +import { TupleN } from './util/types.js'; +import { rangeCheck8 } from './gadgets/range-check.js'; + +export { preNist, nistSha3, ethereum }; // KECCAK CONSTANTS @@ -64,14 +70,153 @@ const ROUND_CONSTANTS = [ 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n, -].map(Field.from); +]; + +function checkBytesToWord(word: Field, wordBytes: Field[]): void { + let composition = wordBytes.reduce((acc, x, i) => { + const shift = Field.from(2n ** BigInt(8 * i)); + return acc.add(x.mul(shift)); + }, Field.from(0)); + + word.assertEquals(composition); +} // Return a keccak state where all lanes are equal to 0 const getKeccakStateZeros = (): Field[][] => Array.from(Array(KECCAK_DIM), (_) => Array(KECCAK_DIM).fill(Field.from(0))); +// Converts a list of bytes to a matrix of Field elements +function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { + assert(bytestring.length === 200, 'improper bytestring length'); + + const bytestringArray = Array.from(bytestring); + const state: Field[][] = getKeccakStateZeros(); + + for (let y = 0; y < KECCAK_DIM; y++) { + for (let x = 0; x < KECCAK_DIM; x++) { + const idx = BYTES_PER_WORD * (KECCAK_DIM * y + x); + // Create an array containing the 8 bytes starting on idx that correspond to the word in [x,y] + const wordBytes = bytestringArray.slice(idx, idx + BYTES_PER_WORD); + + for (let z = 0; z < BYTES_PER_WORD; z++) { + // Field element containing value 2^(8*z) + const shift = Field.from(2n ** BigInt(8 * z)); + state[x][y] = state[x][y].add(shift.mul(wordBytes[z])); + } + } + } + + return state; +} + +// Converts a state of cvars to a list of bytes as cvars and creates constraints for it +function keccakStateToBytes(state: Field[][]): Field[] { + const stateLengthInBytes = KECCAK_STATE_LENGTH / 8; + const bytestring: Field[] = Array.from( + { length: stateLengthInBytes }, + (_, idx) => + existsOne(() => { + // idx = z + 8 * ((dim * y) + x) + const z = idx % BYTES_PER_WORD; + const x = Math.floor(idx / BYTES_PER_WORD) % KECCAK_DIM; + const y = Math.floor(idx / BYTES_PER_WORD / KECCAK_DIM); + // [7 6 5 4 3 2 1 0] [x=0,y=1] [x=0,y=2] [x=0,y=3] [x=0,y=4] + // [x=1,y=0] [x=1,y=1] [x=1,y=2] [x=1,y=3] [x=1,y=4] + // [x=2,y=0] [x=2,y=1] [x=2,y=2] [x=2,y=3] [x=2,y=4] + // [x=3,y=0] [x=3,y=1] [x=3,y=2] [x=3,y=3] [x=3,y=4] + // [x=4,y=0] [x=4,y=1] [x=4,y=0] [x=4,y=3] [x=4,y=4] + const word = state[x][y].toBigInt(); + const byte = (word >> BigInt(8 * z)) & BigInt('0xff'); + return byte; + }) + ); + + // Check all words are composed correctly from bytes + for (let y = 0; y < KECCAK_DIM; y++) { + for (let x = 0; x < KECCAK_DIM; x++) { + const idx = BYTES_PER_WORD * (KECCAK_DIM * y + x); + // Create an array containing the 8 bytes starting on idx that correspond to the word in [x,y] + const word_bytes = bytestring.slice(idx, idx + BYTES_PER_WORD); + // Assert correct decomposition of bytes from state + checkBytesToWord(state[x][y], word_bytes); + } + } + + return bytestring; +} + +function keccakStateXor(a: Field[][], b: Field[][]): Field[][] { + assert( + a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, + 'Invalid input1 dimensions' + ); + assert( + b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, + 'Invalid input2 dimensions' + ); + + return a.map((row, rowIndex) => + row.map((element, columnIndex) => + Gadgets.xor(element, b[rowIndex][columnIndex], 64) + ) + ); +} + // KECCAK HASH FUNCTION +// Computes the number of required extra bytes to pad a message of length bytes +function bytesToPad(rate: number, length: number): number { + return Math.floor(rate / 8) - (length % Math.floor(rate / 8)); +} + +// Pads a message M as: +// M || pad[x](|M|) +// Padding rule 0x06 ..0*..1. +// The padded message vector will start with the message vector +// followed by the 0*1 rule to fulfill a length that is a multiple of rate (in bytes) +// (This means a 0110 sequence, followed with as many 0s as needed, and a final 1 bit) +function padNist(message: Field[], rate: number): Field[] { + // Find out desired length of the padding in bytes + // If message is already rate bits, need to pad full rate again + const extraBytes = bytesToPad(rate, message.length); + + // 0x06 0x00 ... 0x00 0x80 or 0x86 + const lastField = BigInt(2) ** BigInt(7); + const last = Field.from(lastField); + + // Create the padding vector + const pad = Array(extraBytes).fill(Field.from(0)); + pad[0] = Field.from(6); + pad[extraBytes - 1] = pad[extraBytes - 1].add(last); + + // Return the padded message + return [...message, ...pad]; +} + +// Pads a message M as: +// M || pad[x](|M|) +// Padding rule 10*1. +// The padded message vector will start with the message vector +// followed by the 10*1 rule to fulfill a length that is a multiple of rate (in bytes) +// (This means a 1 bit, followed with as many 0s as needed, and a final 1 bit) +function pad101(message: Field[], rate: number): Field[] { + // Find out desired length of the padding in bytes + // If message is already rate bits, need to pad full rate again + const extraBytes = bytesToPad(rate, message.length); + + // 0x01 0x00 ... 0x00 0x80 or 0x81 + const lastField = BigInt(2) ** BigInt(7); + const last = Field.from(lastField); + + // Create the padding vector + const pad = Array(extraBytes).fill(Field.from(0)); + pad[0] = Field.from(1); + pad[extraBytes - 1] = pad[extraBytes - 1].add(last); + + // Return the padded message + return [...message, ...pad]; +} + // ROUND TRANSFORMATION // First algorithm in the compression step of Keccak for 64-bit words. @@ -209,18 +354,230 @@ function permutation(state: Field[][], rc: Field[]): Field[][] { ); } -// TESTING +// Absorb padded message into a keccak state with given rate and capacity +function absorb( + paddedMessage: Field[], + capacity: number, + rate: number, + rc: Field[] +): Field[][] { + let state = getKeccakStateZeros(); -const blockTransformation = (state: Field[][]): Field[][] => - permutation(state, ROUND_CONSTANTS); + // split into blocks of rate bits + // for each block of rate bits in the padded message -> this is rate/8 bytes + const chunks = []; + // (capacity / 8) zero bytes + const zeros = Array(capacity / 8).fill(Field.from(0)); -export { - KECCAK_DIM, - ROUND_CONSTANTS, - theta, - piRho, - chi, - iota, - round, - blockTransformation, -}; + for (let i = 0; i < paddedMessage.length; i += rate / 8) { + const block = paddedMessage.slice(i, i + rate / 8); + // pad the block with 0s to up to 1600 bits + const paddedBlock = block.concat(zeros); + // padded with zeros each block until they are 1600 bit long + assert( + paddedBlock.length * 8 === KECCAK_STATE_LENGTH, + 'improper Keccak block length' + ); + const blockState = getKeccakStateOfBytes(paddedBlock); + // xor the state with the padded block + const stateXor = keccakStateXor(state, blockState); + // apply the permutation function to the xored state + const statePerm = permutation(stateXor, rc); + state = statePerm; + } + + return state; +} + +// Squeeze state until it has a desired length in bits +function squeeze( + state: Field[][], + length: number, + rate: number, + rc: Field[] +): Field[] { + const copy = ( + bytestring: Field[], + outputArray: Field[], + start: number, + length: number + ) => { + for (let i = 0; i < length; i++) { + outputArray[start + i] = bytestring[i]; + } + }; + + let newState = state; + + // bytes per squeeze + const bytesPerSqueeze = rate / 8; + // number of squeezes + const squeezes = Math.floor(length / rate) + 1; + // multiple of rate that is larger than output_length, in bytes + const outputLength = squeezes * bytesPerSqueeze; + // array with sufficient space to store the output + const outputArray = Array(outputLength).fill(Field.from(0)); + // first state to be squeezed + const bytestring = keccakStateToBytes(state); + const outputBytes = bytestring.slice(0, bytesPerSqueeze); + copy(outputBytes, outputArray, 0, bytesPerSqueeze); + // for the rest of squeezes + for (let i = 1; i < squeezes; i++) { + // apply the permutation function to the state + newState = permutation(newState, rc); + // append the output of the permutation function to the output + const bytestringI = keccakStateToBytes(state); + const outputBytesI = bytestringI.slice(0, bytesPerSqueeze); + copy(outputBytesI, outputArray, bytesPerSqueeze * i, bytesPerSqueeze); + } + // Obtain the hash selecting the first bitlength/8 bytes of the output array + const hashed = outputArray.slice(0, length / 8); + + return hashed; +} + +// Keccak sponge function for 1600 bits of state width +// Need to split the message into blocks of 1088 bits. +function sponge( + paddedMessage: Field[], + length: number, + capacity: number, + rate: number +): Field[] { + // check that the padded message is a multiple of rate + if ((paddedMessage.length * 8) % rate !== 0) { + throw new Error('Invalid padded message length'); + } + + // setup cvars for round constants + let rc = exists(24, () => TupleN.fromArray(24, ROUND_CONSTANTS)); + + // absorb + const state = absorb(paddedMessage, capacity, rate, rc); + + // squeeze + const hashed = squeeze(state, length, rate, rc); + + return hashed; +} + +// TODO(jackryanservia): Use lookup argument once issue is resolved +// Checks in the circuit that a list of cvars are at most 8 bits each +function checkBytes(inputs: Field[]): void { + inputs.map(rangeCheck8); +} + +// Keccak hash function with input message passed as list of Cvar bytes. +// The message will be parsed as follows: +// - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) +// - the 10*1 pad will take place after the message, until reaching the bit length rate. +// - then, {0} pad will take place to finish the 1600 bits of the state. +function hash( + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false, + message: Field[] = [], + length: number, + capacity: number, + nistVersion: boolean +): Field[] { + assert(capacity > 0, 'capacity must be positive'); + assert(capacity < KECCAK_STATE_LENGTH, 'capacity must be less than 1600'); + assert(length > 0, 'length must be positive'); + assert(length % 8 === 0, 'length must be a multiple of 8'); + + // Set input to Big Endian format + let messageFormatted = inpEndian === 'Big' ? message : message.reverse(); + + // Check each cvar input is 8 bits at most if it was not done before at creation time + if (byteChecks) { + checkBytes(messageFormatted); + } + + const rate = KECCAK_STATE_LENGTH - capacity; + + let padded; + if (nistVersion) { + padded = padNist(messageFormatted, rate); + } else { + padded = pad101(messageFormatted, rate); + } + + const hash = sponge(padded, length, capacity, rate); + + // Check each cvar output is 8 bits at most. Always because they are created here + checkBytes(hash); + + // Set input to desired endianness + const hashFormatted = outEndian === 'Big' ? hash : hash.reverse(); + + // Check each cvar output is 8 bits at most + return hashFormatted; +} + +// Gadget for NIST SHA-3 function for output lengths 224/256/384/512. +// Input and output endianness can be specified. Default is big endian. +// Note that when calling with output length 256 this is equivalent to the ethereum function +function nistSha3( + len: number, + message: Field[], + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false +): Field[] { + let output: Field[]; + + switch (len) { + case 224: + output = hash(inpEndian, outEndian, byteChecks, message, 224, 448, true); + break; + case 256: + output = hash(inpEndian, outEndian, byteChecks, message, 256, 512, true); + break; + case 384: + output = hash(inpEndian, outEndian, byteChecks, message, 384, 768, true); + break; + case 512: + output = hash(inpEndian, outEndian, byteChecks, message, 512, 1024, true); + break; + default: + throw new Error('Invalid length'); + } + + return output; +} + +// Gadget for Keccak hash function for the parameters used in Ethereum. +// Input and output endianness can be specified. Default is big endian. +function ethereum( + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false, + message: Field[] = [] +): Field[] { + return hash(inpEndian, outEndian, byteChecks, message, 256, 512, false); +} + +// Gadget for pre-NIST SHA-3 function for output lengths 224/256/384/512. +// Input and output endianness can be specified. Default is big endian. +// Note that when calling with output length 256 this is equivalent to the ethereum function +function preNist( + len: number, + message: Field[], + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false +): Field[] { + switch (len) { + case 224: + return hash(inpEndian, outEndian, byteChecks, message, 224, 448, false); + case 256: + return ethereum(inpEndian, outEndian, byteChecks, message); + case 384: + return hash(inpEndian, outEndian, byteChecks, message, 384, 768, false); + case 512: + return hash(inpEndian, outEndian, byteChecks, message, 512, 1024, false); + default: + throw new Error('Invalid length'); + } +} diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 1c20b2ec6f..cd477ddb5c 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,106 +1,63 @@ import { Field } from './field.js'; import { Provable } from './provable.js'; +import { preNist, nistSha3, ethereum } from './keccak.js'; +import { sha3_256, keccak_256 } from '@noble/hashes/sha3'; import { ZkProgram } from './proof_system.js'; -import { constraintSystem, print } from './testing/constraint-system.js'; -import { - KECCAK_DIM, - ROUND_CONSTANTS, - theta, - piRho, - chi, - iota, - round, - blockTransformation, -} from './keccak.js'; -const KECCAK_TEST_STATE = [ - [0, 0, 0, 0, 0], - [0, 0, 1, 0, 0], - [1, 0, 0, 0, 0], - [0, 0, 0, 1, 0], - [0, 1, 0, 0, 0], -].map((row) => row.map((elem) => Field.from(elem))); - -let KeccakBlockTransformation = ZkProgram({ - name: 'KeccakBlockTransformation', - publicInput: Provable.Array(Provable.Array(Field, KECCAK_DIM), KECCAK_DIM), - publicOutput: Provable.Array(Provable.Array(Field, KECCAK_DIM), KECCAK_DIM), +let Keccak = ZkProgram({ + name: 'keccak', + publicInput: Provable.Array(Field, 100), + publicOutput: Provable.Array(Field, 32), methods: { - Theta: { - privateInputs: [], - method(input: Field[][]) { - return theta(input); - }, - }, - PiRho: { + preNist: { privateInputs: [], - method(input: Field[][]) { - return piRho(input); + method(preImage) { + return preNist(256, preImage); }, }, - Chi: { + nistSha3: { privateInputs: [], - method(input: Field[][]) { - return chi(input); + method(preImage) { + return nistSha3(256, preImage); }, }, - Iota: { + ethereum: { privateInputs: [], - method(input: Field[][]) { - return iota(input, ROUND_CONSTANTS[0]); - }, - }, - Round: { - privateInputs: [], - method(input: Field[][]) { - return round(input, ROUND_CONSTANTS[0]); - }, - }, - BlockTransformation: { - privateInputs: [], - method(input: Field[][]) { - return blockTransformation(input); + method(preImage) { + return ethereum(preImage); }, }, }, }); -// constraintSystem.fromZkProgram( -// KeccakBlockTransformation, -// 'BlockTransformation', -// print -// ); - -console.log('KECCAK_TEST_STATE: ', KECCAK_TEST_STATE.toString()); - -console.log('Compiling...'); -await KeccakBlockTransformation.compile(); -console.log('Done!'); -console.log('Generating proof...'); -let proof0 = await KeccakBlockTransformation.BlockTransformation( - KECCAK_TEST_STATE -); -console.log('Done!'); -console.log('Output:', proof0.publicOutput.toString()); -console.log('Verifying...'); -proof0.verify(); -console.log('Done!'); - -/* -[RUST IMPLEMENTATION OUTPUT](https://github.com/BaldyAsh/keccak-rust) - -INPUT: -[[0, 0, 0, 0, 0], - [0, 0, 1, 0, 0], - [1, 0, 0, 0, 0], - [0, 0, 0, 1, 0], - [0, 1, 0, 0, 0]] - -OUTPUT: -[[8771753707458093707, 14139250443469741764, 11827767624278131459, 2757454755833177578, 5758014717183214102], -[3389583698920935946, 1287099063347104936, 15030403046357116816, 17185756281681305858, 9708367831595350450], -[1416127551095004411, 16037937966823201128, 9518790688640222300, 1997971396112921437, 4893561083608951508], -[8048617297177300085, 10306645194383020789, 2789881727527423094, 7603160281577405588, 12935834807086847890], -[9476112750389234330, 13193683191463706918, 4460519148532423021, 7183125267124224670, 1393214916959060614]] -*/ +console.log("compiling keccak"); +await Keccak.compile(); +console.log("done compiling keccak"); + +const runs = 2; + +let preImage = [ + 236, 185, 24, 61, 138, 249, 61, 13, 226, 103, 152, 232, 104, 234, 170, 26, + 46, 54, 157, 146, 17, 240, 10, 193, 214, 110, 134, 47, 97, 241, 172, 198, + 80, 95, 136, 185, 62, 156, 246, 210, 207, 129, 93, 162, 215, 77, 3, 38, + 194, 86, 75, 100, 64, 87, 6, 18, 4, 159, 235, 53, 87, 124, 216, 241, 179, + 201, 111, 168, 72, 181, 28, 65, 142, 243, 224, 69, 58, 178, 114, 3, 112, + 23, 15, 208, 103, 231, 114, 64, 89, 172, 240, 81, 27, 215, 129, 3, 16, + 173, 133, 160, +] + +let preNistProof = await Keccak.preNist(preImage.map(Field.from)); +console.log(preNistProof.publicOutput.toString()); +console.log(keccak_256(new Uint8Array(preImage))); +let nistSha3Proof = await Keccak.nistSha3(preImage.map(Field.from)); +console.log(nistSha3Proof.publicOutput.toString()); +console.log(sha3_256(new Uint8Array(preImage))); +let ethereumProof = await Keccak.ethereum(preImage.map(Field.from)); +console.log(ethereumProof.publicOutput.toString()); + +console.log('verifying'); +preNistProof.verify(); +nistSha3Proof.verify(); +ethereumProof.verify(); +console.log('done verifying'); From 8530e25a4c1dd05d4740cc303ba0c155f43ac486 Mon Sep 17 00:00:00 2001 From: Tang Jiawei Date: Tue, 5 Dec 2023 03:15:01 +0800 Subject: [PATCH 0989/1215] update o1js bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 21ced18c8a..21cf4644f6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 21ced18c8a94a12f653ae85b94c28d9e0bf7f376 +Subproject commit 21cf4644f659998997a0a1a7efc0ed0e038003c2 From 621d2ef1bde826398e5a92fe3e58ced6a73d43e9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 20:17:14 +0100 Subject: [PATCH 0990/1215] fixups --- src/lib/foreign-field.ts | 4 +-- src/lib/gadgets/foreign-field.ts | 2 +- src/lib/gadgets/gadgets.ts | 52 ++++++++++++++++---------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index d5ef7447b6..8b705d620f 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -157,7 +157,7 @@ class ForeignField { // TODO: this is not very efficient, but the only way to abstract away the complicated // range check assumptions and also not introduce a global context of pending range checks. // we plan to get rid of bounds checks anyway, then this is just a multi-range check - Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, { + Gadgets.ForeignField.assertAlmostReduced([this.value], this.modulus, { skipMrc: true, }); return this.Constructor.AlmostReduced.unsafeFrom(this); @@ -172,7 +172,7 @@ class ForeignField { static assertAlmostReduced>( ...xs: T ): TupleMap { - Gadgets.ForeignField.assertAlmostFieldElements( + Gadgets.ForeignField.assertAlmostReduced( xs.map((x) => x.value), this.modulus, { skipMrc: true } diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 46dc63043b..1a8c0d8786 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -10,7 +10,7 @@ import { Bool } from '../bool.js'; import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; -import { Tuple, TupleN, TupleN } from '../util/types.js'; +import { Tuple, TupleN } from '../util/types.js'; import { assertOneOf } from './basic.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; import { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 4a2102bdcb..79bdaf5770 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -570,33 +570,33 @@ const Gadgets = { assertAlmostReduced(xs: Field3[], f: bigint, { skipMrc = false } = {}) { ForeignField.assertAlmostReduced(xs, f, skipMrc); }, - }, - /** - * Prove that x < f for any constant f < 2^264. - * - * 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.assertAlmostFieldElements} - * and also uses more constraints; it should not be needed in most use cases. - * - * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to - * {@link ForeignField.assertAlmostFieldElements} which adds that check itself. - * - * @throws if x is greater or equal to f. - * - * @example - * ```ts - * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); - * - * // range check limbs of x - * Gadgets.multiRangeCheck(x); - * - * // prove that x is fully reduced mod f - * Gadgets.ForeignField.assertLessThan(x, f); - * ``` - */ - assertLessThan(x: Field3, f: bigint) { - ForeignField.assertLessThan(x, f); + /** + * Prove that x < f for any constant f < 2^264. + * + * 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} + * and also uses more constraints; it should not be needed in most use cases. + * + * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to + * {@link ForeignField.assertAlmostReduced} which adds that check itself. + * + * @throws if x is greater or equal to f. + * + * @example + * ```ts + * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); + * + * // range check limbs of x + * Gadgets.multiRangeCheck(x); + * + * // prove that x is fully reduced mod f + * Gadgets.ForeignField.assertLessThan(x, f); + * ``` + */ + assertLessThan(x: Field3, f: bigint) { + ForeignField.assertLessThan(x, f); + }, }, /** From e8918f05c667710c15973044a2de5ce22c3dfe5d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 4 Dec 2023 20:35:55 +0100 Subject: [PATCH 0991/1215] Add vk hash to the return type of compile --- src/lib/proof_system.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 08adef5f68..c7948f22af 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -263,7 +263,7 @@ function ZkProgram< compile: (options?: { cache?: Cache; forceRecompile?: boolean; - }) => Promise<{ verificationKey: string }>; + }) => Promise<{ verificationKey: { data: string, hash: Field } }>; verify: ( proof: Proof< InferProvableOrUndefined>, @@ -361,7 +361,7 @@ function ZkProgram< overrideWrapDomain: config.overrideWrapDomain, }); compileOutput = { provers, verify }; - return { verificationKey: verificationKey.data }; + return { verificationKey }; } function toProver( From f9aa51e1bbeb0f96b78df63cb6a13678e0a20d91 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 4 Dec 2023 20:59:39 +0100 Subject: [PATCH 0992/1215] Added changelog --- CHANGELOG.md | 4 ++++ src/lib/proof_system.ts | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f1b8d4af3..67e2bcd567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +### Breaking changes + +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1240 + ### Added - **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index c7948f22af..9d0a5fdcec 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -260,10 +260,9 @@ function ZkProgram< } ): { name: string; - compile: (options?: { - cache?: Cache; - forceRecompile?: boolean; - }) => Promise<{ verificationKey: { data: string, hash: Field } }>; + compile: (options?: { cache?: Cache; forceRecompile?: boolean }) => Promise<{ + verificationKey: { data: string; hash: Field } + }>; verify: ( proof: Proof< InferProvableOrUndefined>, From e9c21b0f41aa26475a1a9a41825076606bc3922b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 21:49:42 +0100 Subject: [PATCH 0993/1215] update benchmark --- src/examples/benchmarks/foreign-field.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts index cff76159a8..fb32439e0f 100644 --- a/src/examples/benchmarks/foreign-field.ts +++ b/src/examples/benchmarks/foreign-field.ts @@ -1,17 +1,18 @@ -import { Scalar, Crypto, Provable, createForeignField } from 'o1js'; +import { Crypto, Provable, createForeignField } from 'o1js'; class ForeignScalar extends createForeignField( Crypto.CurveParams.Secp256k1.modulus -).AlmostReduced {} - -// TODO ForeignField.random() -function random() { - return new ForeignScalar(Scalar.random().toBigInt()); -} +) {} function main() { - let s = Provable.witness(ForeignScalar.provable, random); - let t = Provable.witness(ForeignScalar.provable, random); + let s = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); + let t = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); s.mul(t); } From a668916de262ac459b30ecd1a8f796651d8708eb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 21:51:02 +0100 Subject: [PATCH 0994/1215] move ecdsa example to where it makes more sense --- src/examples/crypto/ecdsa.ts | 38 ------------------- .../{zkprogram => crypto}/ecdsa/ecdsa.ts | 0 .../{zkprogram => crypto}/ecdsa/run.ts | 0 3 files changed, 38 deletions(-) delete mode 100644 src/examples/crypto/ecdsa.ts rename src/examples/{zkprogram => crypto}/ecdsa/ecdsa.ts (100%) rename src/examples/{zkprogram => crypto}/ecdsa/run.ts (100%) diff --git a/src/examples/crypto/ecdsa.ts b/src/examples/crypto/ecdsa.ts deleted file mode 100644 index 276c6b7dec..0000000000 --- a/src/examples/crypto/ecdsa.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Crypto, createForeignCurve, createEcdsa, Provable } from 'o1js'; - -class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} - -class EthSignature extends createEcdsa(Secp256k1) {} - -let publicKey = Secp256k1.from({ - x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, - y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n, -}); - -let signature = EthSignature.fromHex( - '0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b' -); - -let msgHash = - Secp256k1.Scalar.from( - 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn - ); - -function main() { - let signature0 = Provable.witness(EthSignature.provable, () => signature); - signature0.verify(msgHash, publicKey); -} - -console.time('ecdsa verify (constant)'); -main(); -console.timeEnd('ecdsa verify (constant)'); - -console.time('ecdsa verify (witness gen / check)'); -Provable.runAndCheck(main); -console.timeEnd('ecdsa verify (witness gen / check)'); - -console.time('ecdsa verify (build constraint system)'); -let cs = Provable.constraintSystem(main); -console.timeEnd('ecdsa verify (build constraint system)'); - -console.log(cs.summary()); diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts similarity index 100% rename from src/examples/zkprogram/ecdsa/ecdsa.ts rename to src/examples/crypto/ecdsa/ecdsa.ts diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts similarity index 100% rename from src/examples/zkprogram/ecdsa/run.ts rename to src/examples/crypto/ecdsa/run.ts From 8cc376f4b331598c4d348da536551c3db75f5cef Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 21:55:43 +0100 Subject: [PATCH 0995/1215] doccomment tweaks --- src/lib/foreign-ecdsa.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 1d5ab5ffc9..5e3050e4f1 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -90,7 +90,7 @@ class EcdsaSignature { >; /** - * Curve arithmetic on JS bigints. + * The {@link ForeignCurve} on which the ECDSA signature is defined. */ static get Curve() { assert(this._Curve !== undefined, 'EcdsaSignature not initialized'); @@ -106,8 +106,8 @@ class EcdsaSignature { } /** - * Returns a class {@link EcdsaSignature} enabling to parse and verify ECDSA signature in provable code, - * for the given curve. + * Returns a class {@link EcdsaSignature} enabling to verify ECDSA signatures + * on the given curve, in provable code. */ function createEcdsa(curve: CurveParams | typeof ForeignCurve) { let Curve0: typeof ForeignCurve = From 43337621a03b8b7566ee29cb103ef7e305af74ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 21:59:04 +0100 Subject: [PATCH 0996/1215] revert unnecessary change --- src/lib/ml/fields.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/lib/ml/fields.ts b/src/lib/ml/fields.ts index 0c092dcdfc..4921e9272a 100644 --- a/src/lib/ml/fields.ts +++ b/src/lib/ml/fields.ts @@ -1,7 +1,6 @@ -import { Bool, BoolVar } from '../bool.js'; import { ConstantField, Field, FieldConst, FieldVar } from '../field.js'; import { MlArray } from './base.js'; -export { MlFieldArray, MlFieldConstArray, MlBoolArray }; +export { MlFieldArray, MlFieldConstArray }; type MlFieldArray = MlArray; const MlFieldArray = { @@ -22,13 +21,3 @@ const MlFieldConstArray = { return arr.map((x) => new Field(x) as ConstantField); }, }; - -type MlBoolArray = MlArray; -const MlBoolArray = { - to(arr: Bool[]): MlArray { - return MlArray.to(arr.map((x) => x.value)); - }, - from([, ...arr]: MlArray) { - return arr.map((x) => new Bool(x)); - }, -}; From c6c39ccc51c13cedfdf96c6c22e99b48f5e1f201 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 22:06:29 +0100 Subject: [PATCH 0997/1215] save constraints --- src/lib/foreign-curve.ts | 4 ++-- src/lib/foreign-ecdsa.ts | 5 +++++ tests/vk-regression/vk-regression.json | 10 +++++----- tests/vk-regression/vk-regression.ts | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 7367024a75..f8018e845b 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -160,8 +160,8 @@ class ForeignCurve { * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. */ static check(g: ForeignCurve) { - this.Field.check(g.x); - this.Field.check(g.y); + // more efficient than the automatic check, which would do this for each field separately + this.Field.assertAlmostReduced(g.x, g.y); this.assertOnCurve(g); this.assertInSubgroup(g); } diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 5e3050e4f1..0615853ccd 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -79,6 +79,11 @@ class EcdsaSignature { return new this({ r, s }); } + static check(signature: EcdsaSignature) { + // more efficient than the automatic check, which would do this for each scalar separately + this.Curve.Scalar.assertAlmostReduced(signature.r, signature.s); + } + // dynamic subclassing infra get Constructor() { return this.constructor as typeof EcdsaSignature; diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 7d90f28524..988a1bc731 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "f89c0d140264e085d085f52999fc67d8e3db3909627d8e3e1a087c319853933", + "digest": "36c90ec17088e536562b06f333be6fc1bed252b47a3cfb4e4c24aa22e7a1247b", "methods": { "verifyEcdsa": { - "rows": 38912, - "digest": "5729fe17ff1af33e643a2f7e08292232" + "rows": 38888, + "digest": "70f148db469f028f1d8cb99e8e8e278a" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJ5BebLL3mIFBjZTEd6aY0P+vdDIg7SU4di7OM61CjQErcEn5Dyn9twBE9Rx7HN+Rix7+FJy63pyr43bG0OoID4gKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgvnOM9xbrxf3uNXY+sxBV1C0Y1XMbxUBeg2319X1diC8anDkLuiR5M6RL9jqRVurwp8dpL1mlssvdUMEAWVbRGV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "692488770088381683600018326540220594385299569804743037992171368071788655058" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAP8r6+oM655IWrhl71ElRkB6sgc6QcECWuPagIo3jpwP7Up1gE0toPdVWzkPhwnFfmujzGZV30q1C/BVIM9icQcgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgD6ZSfWTUrxRxm0kAJzNwO5p4hhA1FjRzX3p0diEhpjyyb6kXOYxmXiAGan/A9/KoYBCxLwtJpn4CgyRa0C73EV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "5175094130394897519519312310597261032080672241911883655552182168739967574124" } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index a051f21137..5d2c6d71e9 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,7 +3,7 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; -import { ecdsaProgram } from '../../src/examples/zkprogram/ecdsa/ecdsa.js'; +import { ecdsaProgram } from '../../src/examples/crypto/ecdsa/ecdsa.js'; import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions From 9590840c5c969580f17bc755d7dd71e35e91c7d4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 22:35:17 +0100 Subject: [PATCH 0998/1215] add ec gadgets --- src/lib/gadgets/elliptic-curve.ts | 5 +- src/lib/gadgets/gadgets.ts | 104 +++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 546a15e100..1372ce5cef 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -202,9 +202,8 @@ function scale( // checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 function assertInSubgroup(Curve: CurveAffine, p: Point) { - const order = Field3.from(Curve.order); - // [order]g = 0 - scale(Curve, order, p, { mode: 'assert-zero' }); + if (!Curve.hasCofactor) return; + scale(Curve, Field3.from(Curve.order), p, { mode: 'assert-zero' }); } // check whether a point equals a constant point diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index f229d7923e..0be939bdce 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -10,7 +10,7 @@ import { import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../field.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; -import { Ecdsa, Point } from './elliptic-curve.js'; +import { Ecdsa, EllipticCurve, Point } from './elliptic-curve.js'; import { CurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { Crypto } from '../crypto.js'; @@ -599,11 +599,74 @@ const Gadgets = { }, }, + /** + * Operations on elliptic curves in non-native arithmetic. + */ + EllipticCurve: { + /** + * Elliptic curve addition. + */ + add(p: Point, q: Point, Curve: { modulus: bigint }) { + return EllipticCurve.add(p, q, Curve.modulus); + }, + + /** + * Elliptic curve doubling. + */ + double(p: Point, Curve: { modulus: bigint; a: bigint }) { + return EllipticCurve.double(p, Curve.modulus, Curve.a); + }, + + /** + * Elliptic curve negation. + */ + negate(p: Point, Curve: { modulus: bigint }) { + return EllipticCurve.negate(p, Curve.modulus); + }, + + /** + * Scalar multiplication. + */ + scale(scalar: Field3, p: Point, Curve: CurveAffine) { + return EllipticCurve.scale(Curve, scalar, p); + }, + + /** + * Multi-scalar multiplication. + */ + scaleMany(scalars: Field3[], points: Point[], Curve: CurveAffine) { + return EllipticCurve.multiScalarMul(Curve, scalars, points); + }, + + /** + * Prove that the given point is on the given curve. + * + * @example + * ```ts + * Gadgets.ForeignCurve.assertPointOnCurve(point, Curve); + * ``` + */ + assertOnCurve(p: Point, Curve: { modulus: bigint; a: bigint; b: bigint }) { + EllipticCurve.assertOnCurve(p, Curve); + }, + + /** + * Prove that the given point is in the prime-order subgroup of the given curve. + * + * @example + * ```ts + * Gadgets.ForeignCurve.assertInSubgroup(point, Curve); + * ``` + */ + assertInSubgroup(p: Point, Curve: CurveAffine) { + EllipticCurve.assertInSubgroup(Curve, p); + }, + }, + /** * ECDSA verification gadget and helper methods. */ Ecdsa: { - // TODO add an easy way to prove that the public key lies on the curve, and show in the example /** * Verify an ECDSA signature. * @@ -620,6 +683,14 @@ const Gadgets = { * Curve.order * ); * + * // assert that the public key is valid + * Gadgets.ForeignField.assertAlmostReduced( + * [publicKey.x, publicKey.y], + * Curve.modulus + * ); + * Gadgets.EllipticCurve.assertOnCurve(publicKey, Curve); + * Gadgets.EllipticCurve.assertInSubgroup(publicKey, Curve); + * * // verify signature * let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); * isValid.assertTrue(); @@ -674,6 +745,13 @@ export namespace Gadgets { export type Sum = Sum_; } + /** + * Non-zero elliptic curve point in affine coordinates. + * + * The coordinates are represented as 3-limb bigints. + */ + export type Point = Point_; + export namespace Ecdsa { /** * ECDSA signature consisting of two curve scalars. @@ -683,5 +761,27 @@ export namespace Gadgets { } } type Sum_ = Sum; +type Point_ = Point; type EcdsaSignature = Ecdsa.Signature; type ecdsaSignature = Ecdsa.signature; + +function verify(signature: Ecdsa.Signature, msgHash: Field3, publicKey: Point) { + const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); + // assert that message hash and signature are valid scalar field elements + Gadgets.ForeignField.assertAlmostReduced( + [signature.r, signature.s, msgHash], + Curve.order + ); + + // assert that the public key is valid + Gadgets.ForeignField.assertAlmostReduced( + [publicKey.x, publicKey.y], + Curve.modulus + ); + Gadgets.EllipticCurve.assertOnCurve(publicKey, Curve); + Gadgets.EllipticCurve.assertInSubgroup(publicKey, Curve); + + // verify signature + let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); + isValid.assertTrue(); +} From 49b98c53e98bbd49e3b171e5b950795d62480b81 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 22:43:39 +0100 Subject: [PATCH 0999/1215] adapt constant case in multiscalarmul, remove the one in scale --- src/lib/gadgets/elliptic-curve.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 1372ce5cef..dc8fba5eb0 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -182,20 +182,6 @@ function scale( multiples?: Point[]; } = { mode: 'assert-nonzero' } ) { - // constant case - if (Field3.isConstant(scalar) && Point.isConstant(point)) { - let scalar_ = Field3.toBigint(scalar); - let p_ = Point.toBigint(point); - let scaled = Curve.scale(p_, scalar_); - if (config.mode === 'assert-zero') { - assert(scaled.infinity, 'scale: expected zero result'); - return Point.from(Curve.zero); - } - assert(!scaled.infinity, 'scale: expected non-zero result'); - return Point.from(scaled); - } - - // provable case config.windowSize ??= Point.isConstant(point) ? 4 : 3; return multiScalarMul(Curve, [scalar], [point], [config], config.mode); } @@ -331,6 +317,11 @@ function multiScalarMul( for (let i = 0; i < n; i++) { sum = Curve.add(sum, Curve.scale(P[i], s[i])); } + if (mode === 'assert-zero') { + assert(sum.infinity, 'scalar multiplication: expected zero result'); + return Point.from(Curve.zero); + } + assert(!sum.infinity, 'scalar multiplication: expected non-zero result'); return Point.from(sum); } From cbd335f6290ea61c9d65252494e96a0ad3e84c0d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 22:45:56 +0100 Subject: [PATCH 1000/1215] remove testing code --- src/lib/gadgets/gadgets.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 0be939bdce..064e63527e 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -764,24 +764,3 @@ type Sum_ = Sum; type Point_ = Point; type EcdsaSignature = Ecdsa.Signature; type ecdsaSignature = Ecdsa.signature; - -function verify(signature: Ecdsa.Signature, msgHash: Field3, publicKey: Point) { - const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); - // assert that message hash and signature are valid scalar field elements - Gadgets.ForeignField.assertAlmostReduced( - [signature.r, signature.s, msgHash], - Curve.order - ); - - // assert that the public key is valid - Gadgets.ForeignField.assertAlmostReduced( - [publicKey.x, publicKey.y], - Curve.modulus - ); - Gadgets.EllipticCurve.assertOnCurve(publicKey, Curve); - Gadgets.EllipticCurve.assertInSubgroup(publicKey, Curve); - - // verify signature - let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); - isValid.assertTrue(); -} From 9ebd5f3dc1cff27eb5de46b369e984d1f0b9521e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 23:18:40 +0100 Subject: [PATCH 1001/1215] start writing ec gadgets tests --- src/bindings | 2 +- src/lib/gadgets/ecdsa.unit-test.ts | 6 +- src/lib/gadgets/elliptic-curve.ts | 34 ++++++++++- src/lib/gadgets/elliptic-curve.unit-test.ts | 66 +++++++++++++++++++++ src/lib/gadgets/gadgets.ts | 5 ++ 5 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/lib/gadgets/elliptic-curve.unit-test.ts diff --git a/src/bindings b/src/bindings index 6c3e61f002..f9fc0b9115 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 6c3e61f002cf9c5dd3aa9374c3162a98786b3880 +Subproject commit f9fc0b91157f2cea3dad2acce256ad879cef86fa diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index d2b9e5b836..66789af22c 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -1,8 +1,8 @@ import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { Ecdsa, - EllipticCurve, Point, + initialAggregator, verifyEcdsaConstant, } from './elliptic-curve.js'; import { Field3 } from './foreign-field.js'; @@ -10,7 +10,7 @@ import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; -import { foreignField, throwError, uniformForeignField } from './test-utils.js'; +import { foreignField, uniformForeignField } from './test-utils.js'; import { Second, bool, @@ -96,7 +96,7 @@ let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn ); -const ia = EllipticCurve.initialAggregator(Secp256k1); +const ia = initialAggregator(Secp256k1); const config = { G: { windowSize: 4 }, P: { windowSize: 3 }, ia }; let program = ZkProgram({ diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index dc8fba5eb0..894445019e 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -24,7 +24,7 @@ import { arrayGet } from './basic.js'; export { EllipticCurve, Point, Ecdsa }; // internal API -export { verifyEcdsaConstant }; +export { verifyEcdsaConstant, initialAggregator, simpleMapToCurve }; const EllipticCurve = { add, @@ -34,7 +34,6 @@ const EllipticCurve = { scale, assertInSubgroup, multiScalarMul, - initialAggregator, }; /** @@ -475,6 +474,22 @@ function initialAggregator(Curve: CurveAffine) { // use that as x coordinate const F = Curve.Field; let x = F.mod(bytesToBigInt(bytes)); + return simpleMapToCurve(x, Curve); +} + +function random(Curve: CurveAffine) { + let x = Curve.Field.random(); + return simpleMapToCurve(x, Curve); +} + +/** + * Given an x coordinate (base field element), increment it until we find one with + * a y coordinate that satisfies the curve equation, and return the point. + * + * If the curve has a cofactor, multiply by it to get a point in the correct subgroup. + */ +function simpleMapToCurve(x: bigint, Curve: CurveAffine) { + const F = Curve.Field; let y: bigint | undefined = undefined; // increment x until we find a y coordinate @@ -485,7 +500,13 @@ function initialAggregator(Curve: CurveAffine) { let y2 = F.add(x3, F.mul(Curve.a, x) + Curve.b); y = F.sqrt(y2); } - return { x, y, infinity: false }; + let p = { x, y, infinity: false }; + + // clear cofactor + if (Curve.hasCofactor) { + p = Curve.scale(p, Curve.cofactor!); + } + return p; } /** @@ -615,6 +636,13 @@ const Point = { }, isConstant: (P: Point) => Provable.isConstant(Point.provable, P), + /** + * Random point on the curve. + */ + random(Curve: CurveAffine) { + return Point.from(random(Curve)); + }, + provable: provable({ x: Field3.provable, y: Field3.provable }), }; diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts new file mode 100644 index 0000000000..d9626d9c9b --- /dev/null +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -0,0 +1,66 @@ +import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; +import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; +import { equivalentProvable, map, spec } from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { Point, simpleMapToCurve } from './elliptic-curve.js'; +import { Gadgets } from './gadgets.js'; +import { foreignField } from './test-utils.js'; + +// provable equivalence tests +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); +const Vesta = createCurveAffine(CurveParams.Vesta); +let curves = [Secp256k1, Pallas, Vesta]; + +for (let Curve of curves) { + // prepare test inputs + let field = foreignField(Curve.Field); + let scalar = foreignField(Curve.Scalar); + + // point shape, but with independently random components, which will never form a valid point + let badPoint = spec({ + rng: Random.record({ + x: field.rng, + y: field.rng, + infinity: Random.constant(false), + }), + there: Point.from, + back: Point.toBigint, + provable: Point.provable, + }); + + // valid random points + let point = map({ from: field, to: badPoint }, (x) => + simpleMapToCurve(x, Curve) + ); + + // gadgets + + // add + equivalentProvable({ from: [point, point], to: point })( + Curve.add, + (p, q) => Gadgets.EllipticCurve.add(p, q, Curve), + 'add' + ); + + // double + equivalentProvable({ from: [point], to: point })( + Curve.double, + (p) => Gadgets.EllipticCurve.double(p, Curve), + 'double' + ); + + // negate + equivalentProvable({ from: [point], to: point })( + Curve.negate, + (p) => Gadgets.EllipticCurve.negate(p, Curve), + 'negate' + ); + + // scale + equivalentProvable({ from: [point, scalar], to: point })( + Curve.scale, + (p, s) => Gadgets.EllipticCurve.scale(s, p, Curve), + 'scale' + ); +} diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 064e63527e..051424d51d 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -661,6 +661,11 @@ const Gadgets = { assertInSubgroup(p: Point, Curve: CurveAffine) { EllipticCurve.assertInSubgroup(Curve, p); }, + + /** + * Non-provabe helper methods for interacting with elliptic curves. + */ + Point, }, /** From 7acf19d0db962d78597f70f69a44035c96e33e4d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 5 Dec 2023 00:04:44 +0000 Subject: [PATCH 1002/1215] 0.14.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index eff466f3fb..569c4587cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.14.2", + "version": "0.14.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.14.2", + "version": "0.14.3", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index b890fd3dba..6c3560c2d4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.14.2", + "version": "0.14.3", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From fe359e3052e37ced27094bc31860f1a135cbcba6 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 5 Dec 2023 00:04:51 +0000 Subject: [PATCH 1003/1215] Update CHANGELOG for new version v0.14.3 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f1b8d4af3..de16de9095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) + +## [0.14.3](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) ### Added From 07a1fde0392397b3864abd603b2612a23a7af8d3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:35:26 +0100 Subject: [PATCH 1004/1215] simplify build scripts --- package.json | 6 ++---- run-unit-tests.sh | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b890fd3dba..c432bc9643 100644 --- a/package.json +++ b/package.json @@ -42,19 +42,17 @@ "node": ">=16.4.0" }, "scripts": { - "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", + "dev": "npx tsc -p tsconfig.test.json && node src/build/copy-to-dist.js", "make": "make -C ../../.. snarkyjs", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", "wasm": "./src/bindings/scripts/update-wasm-and-types.sh", "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", - "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", - "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", - "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", + "prepublish:node": "node src/build/copy-artifacts.js && rimraf ./dist/node && npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js && NODE_ENV=production node src/build/buildNode.js", "prepublishOnly": "npm run prepublish:web && npm run prepublish:node", "dump-vks": "./run tests/vk-regression/vk-regression.ts --bundle --dump", "format": "prettier --write --ignore-unknown **/*", diff --git a/run-unit-tests.sh b/run-unit-tests.sh index 44881eca5d..3e39307f36 100755 --- a/run-unit-tests.sh +++ b/run-unit-tests.sh @@ -2,8 +2,7 @@ set -e shopt -s globstar # to expand '**' into nested directories./ -# run the build:test -npm run build:test +npm run build # find all unit tests in dist/node and run them # TODO it would be nice to make this work on Mac From 502a4979a62d119e5a0b543f29290a2186d5bd9f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:36:45 +0100 Subject: [PATCH 1005/1215] equivalent: add verbose option and onlyif combinator --- src/lib/testing/equivalent.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 183281bbba..cff488afca 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -27,6 +27,7 @@ export { array, record, map, + onlyIf, fromRandom, first, second, @@ -186,7 +187,7 @@ function equivalentAsync< function equivalentProvable< In extends Tuple>, Out extends ToSpec ->({ from: fromRaw, to }: { from: In; to: Out }) { +>({ from: fromRaw, to, verbose }: { from: In; to: Out; verbose?: boolean }) { let fromUnions = fromRaw.map(toUnion); return function run( f1: (...args: Params1) => First, @@ -195,7 +196,9 @@ function equivalentProvable< ) { let generators = fromUnions.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; - test(...generators, (...args) => { + + let start = performance.now(); + let nRuns = test.custom({ minRuns: 5 })(...generators, (...args) => { args.pop(); // figure out which spec to use for each argument @@ -230,6 +233,11 @@ function equivalentProvable< ); }); }); + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log(`${label}:\t succeeded with ${runs} runs in ${ms}ms.`); + } }; } @@ -342,6 +350,10 @@ function map( return { ...to, rng: Random.map(from.rng, there) }; } +function onlyIf(spec: Spec, onlyIf: (t: T) => boolean): Spec { + return { ...spec, rng: Random.reject(spec.rng, (x) => !onlyIf(x)) }; +} + function mapObject( t: { [k in K]: T }, map: (t: T, k: K) => S From 578a8adde2dafb8ef0b236e2b8c1ef39d4344a09 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:38:41 +0100 Subject: [PATCH 1006/1215] fix ec test --- src/lib/gadgets/elliptic-curve.unit-test.ts | 32 +++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index d9626d9c9b..69d1aa11c4 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -1,7 +1,14 @@ import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { equivalentProvable, map, spec } from '../testing/equivalent.js'; +import { + array, + equivalentProvable, + map, + onlyIf, + spec, +} from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; +import { assert } from './common.js'; import { Point, simpleMapToCurve } from './elliptic-curve.js'; import { Gadgets } from './gadgets.js'; import { foreignField } from './test-utils.js'; @@ -29,37 +36,44 @@ for (let Curve of curves) { provable: Point.provable, }); - // valid random points + // valid random point let point = map({ from: field, to: badPoint }, (x) => simpleMapToCurve(x, Curve) ); + // two random points that are not equal, so are a valid input to EC addition + let unequalPair = onlyIf(array(point, 2), ([p, q]) => !Curve.equal(p, q)); + // gadgets // add - equivalentProvable({ from: [point, point], to: point })( - Curve.add, - (p, q) => Gadgets.EllipticCurve.add(p, q, Curve), + equivalentProvable({ from: [unequalPair], to: point, verbose: true })( + ([p, q]) => Curve.add(p, q), + ([p, q]) => Gadgets.EllipticCurve.add(p, q, Curve), 'add' ); // double - equivalentProvable({ from: [point], to: point })( + equivalentProvable({ from: [point], to: point, verbose: true })( Curve.double, (p) => Gadgets.EllipticCurve.double(p, Curve), 'double' ); // negate - equivalentProvable({ from: [point], to: point })( + equivalentProvable({ from: [point], to: point, verbose: true })( Curve.negate, (p) => Gadgets.EllipticCurve.negate(p, Curve), 'negate' ); // scale - equivalentProvable({ from: [point, scalar], to: point })( - Curve.scale, + equivalentProvable({ from: [point, scalar], to: point, verbose: true })( + (p, s) => { + let sp = Curve.scale(p, s); + assert(!sp.infinity, 'expect nonzero'); + return sp; + }, (p, s) => Gadgets.EllipticCurve.scale(s, p, Curve), 'scale' ); From a7f997ba1b0cc8cac7de50d3dc932ae45d9a88b0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:53:06 +0100 Subject: [PATCH 1007/1215] add assert on curve --- src/lib/gadgets/elliptic-curve.unit-test.ts | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index 69d1aa11c4..6a16da5de7 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -6,12 +6,12 @@ import { map, onlyIf, spec, + unit, } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; import { assert } from './common.js'; -import { Point, simpleMapToCurve } from './elliptic-curve.js'; -import { Gadgets } from './gadgets.js'; -import { foreignField } from './test-utils.js'; +import { EllipticCurve, Point, simpleMapToCurve } from './elliptic-curve.js'; +import { foreignField, throwError } from './test-utils.js'; // provable equivalence tests const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); @@ -44,37 +44,39 @@ for (let Curve of curves) { // two random points that are not equal, so are a valid input to EC addition let unequalPair = onlyIf(array(point, 2), ([p, q]) => !Curve.equal(p, q)); - // gadgets + // test ec gadgets witness generation - // add equivalentProvable({ from: [unequalPair], to: point, verbose: true })( ([p, q]) => Curve.add(p, q), - ([p, q]) => Gadgets.EllipticCurve.add(p, q, Curve), - 'add' + ([p, q]) => EllipticCurve.add(p, q, Curve.modulus), + `${Curve.name} add` ); - // double equivalentProvable({ from: [point], to: point, verbose: true })( Curve.double, - (p) => Gadgets.EllipticCurve.double(p, Curve), - 'double' + (p) => EllipticCurve.double(p, Curve.modulus, Curve.a), + `${Curve.name} double` ); - // negate equivalentProvable({ from: [point], to: point, verbose: true })( Curve.negate, - (p) => Gadgets.EllipticCurve.negate(p, Curve), - 'negate' + (p) => EllipticCurve.negate(p, Curve.modulus), + `${Curve.name} negate` + ); + + equivalentProvable({ from: [point], to: unit, verbose: true })( + (p) => Curve.isOnCurve(p) || throwError('expect on curve'), + (p) => EllipticCurve.assertOnCurve(p, Curve), + `${Curve.name} on curve` ); - // scale equivalentProvable({ from: [point, scalar], to: point, verbose: true })( (p, s) => { let sp = Curve.scale(p, s); assert(!sp.infinity, 'expect nonzero'); return sp; }, - (p, s) => Gadgets.EllipticCurve.scale(s, p, Curve), - 'scale' + (p, s) => EllipticCurve.scale(Curve, s, p), + `${Curve.name} scale` ); } From 7e83c4373e58852f5b8624a464938559cefefd20 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:53:12 +0100 Subject: [PATCH 1008/1215] minor --- src/lib/testing/equivalent.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index cff488afca..a78a48f17a 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -236,7 +236,9 @@ function equivalentProvable< if (verbose) { let ms = (performance.now() - start).toFixed(1); let runs = nRuns.toString().padStart(2, ' '); - console.log(`${label}:\t succeeded with ${runs} runs in ${ms}ms.`); + console.log( + `${label.padEnd(20, ' ')}\t success on ${runs} runs in ${ms}ms.` + ); } }; } From 43eadf711003f0435b1a74d4cf34fdb5ec0859bd Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:54:47 +0100 Subject: [PATCH 1009/1215] minor console formatting --- src/lib/testing/equivalent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index a78a48f17a..cef3029561 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -237,7 +237,7 @@ function equivalentProvable< let ms = (performance.now() - start).toFixed(1); let runs = nRuns.toString().padStart(2, ' '); console.log( - `${label.padEnd(20, ' ')}\t success on ${runs} runs in ${ms}ms.` + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` ); } }; From 78e0c6369637616f7f6077117bdb82605d3174f9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:08:27 +0100 Subject: [PATCH 1010/1215] standardize ec gadgets inputs --- src/lib/foreign-curve.ts | 14 +++---- src/lib/gadgets/elliptic-curve.ts | 43 +++++++++++---------- src/lib/gadgets/elliptic-curve.unit-test.ts | 8 ++-- src/lib/gadgets/gadgets.ts | 12 +++--- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index f8018e845b..a96f3fd8a9 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -93,8 +93,9 @@ class ForeignCurve { * Elliptic curve addition. */ add(h: ForeignCurve | FlexiblePoint) { + let Curve = this.Constructor.Bigint; let h_ = this.Constructor.from(h); - let p = EllipticCurve.add(toPoint(this), toPoint(h_), this.modulus); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), Curve); return new this.Constructor(p); } @@ -103,7 +104,7 @@ class ForeignCurve { */ double() { let Curve = this.Constructor.Bigint; - let p = EllipticCurve.double(toPoint(this), Curve.modulus, Curve.a); + let p = EllipticCurve.double(toPoint(this), Curve); return new this.Constructor(p); } @@ -130,18 +131,15 @@ class ForeignCurve { * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. */ scale(scalar: AlmostForeignField | bigint | number) { + let Curve = this.Constructor.Bigint; let scalar_ = this.Constructor.Scalar.from(scalar); - let p = EllipticCurve.scale( - this.Constructor.Bigint, - scalar_.value, - toPoint(this) - ); + let p = EllipticCurve.scale(scalar_.value, toPoint(this), Curve); return new this.Constructor(p); } static assertInSubgroup(g: ForeignCurve) { if (this.Bigint.hasCofactor) { - EllipticCurve.assertInSubgroup(this.Bigint, toPoint(g)); + EllipticCurve.assertInSubgroup(toPoint(g), this.Bigint); } } diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 894445019e..426ce43aa3 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -50,9 +50,10 @@ namespace Ecdsa { export type signature = { r: bigint; s: bigint }; } -function add(p1: Point, p2: Point, f: bigint) { +function add(p1: Point, p2: Point, Curve: { modulus: bigint }) { let { x: x1, y: y1 } = p1; let { x: x2, y: y2 } = p2; + let f = Curve.modulus; // constant case if (Point.isConstant(p1) && Point.isConstant(p2)) { @@ -95,8 +96,9 @@ function add(p1: Point, p2: Point, f: bigint) { return { x: x3, y: y3 }; } -function double(p1: Point, f: bigint, a: bigint) { +function double(p1: Point, Curve: { modulus: bigint; a: bigint }) { let { x: x1, y: y1 } = p1; + let f = Curve.modulus; // constant case if (Point.isConstant(p1)) { @@ -128,7 +130,8 @@ function double(p1: Point, f: bigint, a: bigint) { // 2*y1*m = 3*x1x1 + a let y1Times2 = ForeignField.Sum(y1).add(y1); let x1x1Times3PlusA = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); - if (a !== 0n) x1x1Times3PlusA = x1x1Times3PlusA.add(Field3.from(a)); + if (Curve.a !== 0n) + x1x1Times3PlusA = x1x1Times3PlusA.add(Field3.from(Curve.a)); ForeignField.assertMul(y1Times2, m, x1x1Times3PlusA, f); // m^2 = 2*x1 + x3 @@ -143,8 +146,8 @@ function double(p1: Point, f: bigint, a: bigint) { return { x: x3, y: y3 }; } -function negate({ x, y }: Point, f: bigint) { - return { x, y: ForeignField.negate(y, f) }; +function negate({ x, y }: Point, Curve: { modulus: bigint }) { + return { x, y: ForeignField.negate(y, Curve.modulus) }; } function assertOnCurve( @@ -172,9 +175,9 @@ function assertOnCurve( * The result is constrained to be not zero. */ function scale( - Curve: CurveAffine, scalar: Field3, point: Point, + Curve: CurveAffine, config: { mode?: 'assert-nonzero' | 'assert-zero'; windowSize?: number; @@ -182,20 +185,20 @@ function scale( } = { mode: 'assert-nonzero' } ) { config.windowSize ??= Point.isConstant(point) ? 4 : 3; - return multiScalarMul(Curve, [scalar], [point], [config], config.mode); + return multiScalarMul([scalar], [point], Curve, [config], config.mode); } // checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 -function assertInSubgroup(Curve: CurveAffine, p: Point) { +function assertInSubgroup(p: Point, Curve: CurveAffine) { if (!Curve.hasCofactor) return; - scale(Curve, Field3.from(Curve.order), p, { mode: 'assert-zero' }); + scale(Field3.from(Curve.order), p, Curve, { mode: 'assert-zero' }); } // check whether a point equals a constant point // TODO implement the full case of two vars -function equals(p1: Point, p2: point, f: bigint) { - let xEquals = ForeignField.equals(p1.x, p2.x, f); - let yEquals = ForeignField.equals(p1.y, p2.y, f); +function equals(p1: Point, p2: point, Curve: { modulus: bigint }) { + let xEquals = ForeignField.equals(p1.x, p2.x, Curve.modulus); + let yEquals = ForeignField.equals(p1.y, p2.y, Curve.modulus); return xEquals.and(yEquals); } @@ -253,9 +256,9 @@ function verifyEcdsa( let G = Point.from(Curve.one); let R = multiScalarMul( - Curve, [u1, u2], [G, publicKey], + Curve, config && [config.G, config.P], 'assert-nonzero', config?.ia @@ -293,9 +296,9 @@ function verifyEcdsa( * TODO: glv trick which cuts down ec doubles by half by splitting s*P = s0*P + s1*endo(P) with s0, s1 in [0, 2^128) */ function multiScalarMul( - Curve: CurveAffine, scalars: Field3[], points: Point[], + Curve: CurveAffine, tableConfigs: ( | { windowSize?: number; multiples?: Point[] } | undefined @@ -352,7 +355,7 @@ function multiScalarMul( : arrayGetGeneric(Point.provable, tables[j], sj); // ec addition - let added = add(sum, sjP, Curve.modulus); + let added = add(sum, sjP, Curve); // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) sum = Provable.if(sj.equals(0), Point.provable, sum, added); @@ -363,17 +366,17 @@ function multiScalarMul( // jointly double all points // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) - sum = double(sum, Curve.modulus, Curve.a); + sum = double(sum, Curve); } // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(b - 1)); - let isZero = equals(sum, iaFinal, Curve.modulus); + let isZero = equals(sum, iaFinal, Curve); if (mode === 'assert-nonzero') { isZero.assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve); } else { isZero.assertTrue(); // for type consistency with the 'assert-nonzero' case @@ -443,10 +446,10 @@ function getPointTable( table = [Point.from(Curve.zero), P]; if (n === 2) return table; - let Pi = double(P, Curve.modulus, Curve.a); + let Pi = double(P, Curve); table.push(Pi); for (let i = 3; i < n; i++) { - Pi = add(Pi, P, Curve.modulus); + Pi = add(Pi, P, Curve); table.push(Pi); } return table; diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts index 6a16da5de7..39a6d41ce0 100644 --- a/src/lib/gadgets/elliptic-curve.unit-test.ts +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -48,19 +48,19 @@ for (let Curve of curves) { equivalentProvable({ from: [unequalPair], to: point, verbose: true })( ([p, q]) => Curve.add(p, q), - ([p, q]) => EllipticCurve.add(p, q, Curve.modulus), + ([p, q]) => EllipticCurve.add(p, q, Curve), `${Curve.name} add` ); equivalentProvable({ from: [point], to: point, verbose: true })( Curve.double, - (p) => EllipticCurve.double(p, Curve.modulus, Curve.a), + (p) => EllipticCurve.double(p, Curve), `${Curve.name} double` ); equivalentProvable({ from: [point], to: point, verbose: true })( Curve.negate, - (p) => EllipticCurve.negate(p, Curve.modulus), + (p) => EllipticCurve.negate(p, Curve), `${Curve.name} negate` ); @@ -76,7 +76,7 @@ for (let Curve of curves) { assert(!sp.infinity, 'expect nonzero'); return sp; }, - (p, s) => EllipticCurve.scale(Curve, s, p), + (p, s) => EllipticCurve.scale(s, p, Curve), `${Curve.name} scale` ); } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 051424d51d..43e5bc272f 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -607,35 +607,35 @@ const Gadgets = { * Elliptic curve addition. */ add(p: Point, q: Point, Curve: { modulus: bigint }) { - return EllipticCurve.add(p, q, Curve.modulus); + return EllipticCurve.add(p, q, Curve); }, /** * Elliptic curve doubling. */ double(p: Point, Curve: { modulus: bigint; a: bigint }) { - return EllipticCurve.double(p, Curve.modulus, Curve.a); + return EllipticCurve.double(p, Curve); }, /** * Elliptic curve negation. */ negate(p: Point, Curve: { modulus: bigint }) { - return EllipticCurve.negate(p, Curve.modulus); + return EllipticCurve.negate(p, Curve); }, /** * Scalar multiplication. */ scale(scalar: Field3, p: Point, Curve: CurveAffine) { - return EllipticCurve.scale(Curve, scalar, p); + return EllipticCurve.scale(scalar, p, Curve); }, /** * Multi-scalar multiplication. */ scaleMany(scalars: Field3[], points: Point[], Curve: CurveAffine) { - return EllipticCurve.multiScalarMul(Curve, scalars, points); + return EllipticCurve.multiScalarMul(scalars, points, Curve); }, /** @@ -659,7 +659,7 @@ const Gadgets = { * ``` */ assertInSubgroup(p: Point, Curve: CurveAffine) { - EllipticCurve.assertInSubgroup(Curve, p); + EllipticCurve.assertInSubgroup(p, Curve); }, /** From d32ad75471c11e8295dcae4f3e833520522df496 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:17:25 +0100 Subject: [PATCH 1011/1215] make record forward provable --- src/lib/testing/equivalent.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index cef3029561..82bdbbf2e0 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -5,6 +5,7 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; import { Bool, Field } from '../core.js'; +import { provable } from '../circuit_value.js'; export { equivalent, @@ -338,10 +339,14 @@ function record }>( { [k in keyof Specs]: First }, { [k in keyof Specs]: Second } > { + let isProvable = Object.values(specs).every((spec) => spec.provable); return { rng: Random.record(mapObject(specs, (spec) => spec.rng)) as any, there: (x) => mapObject(specs, (spec, k) => spec.there(x[k])) as any, back: (x) => mapObject(specs, (spec, k) => spec.back(x[k])) as any, + provable: isProvable + ? provable(mapObject(specs, (spec) => spec.provable) as any) + : undefined, }; } From 24d3a320a1d0d5beff23c952a5f14095effce9e5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:25:30 +0100 Subject: [PATCH 1012/1215] add missing label --- src/lib/testing/equivalent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 82bdbbf2e0..b4756df727 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -230,7 +230,8 @@ function equivalentProvable< handleErrors( () => f1(...inputs), () => f2(...inputWitnesses), - (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)) + (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)), + label ); }); }); From c07857fe8a52edac14f8f222f3cb0e19c35925f6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:25:49 +0100 Subject: [PATCH 1013/1215] verbose ecdsa test --- src/lib/gadgets/ecdsa.unit-test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 66789af22c..6b47310d38 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -57,26 +57,30 @@ for (let Curve of curves) { }; // positive test - equivalentProvable({ from: [signature], to: bool })( + equivalentProvable({ from: [signature], to: bool, verbose: true })( () => true, verify, - 'valid signature verifies' + `${Curve.name}: valid signature verifies` ); // negative test - equivalentProvable({ from: [badSignature], to: bool })( + equivalentProvable({ from: [badSignature], to: bool, verbose: true })( () => false, verify, - 'invalid signature fails' + `${Curve.name}: invalid signature fails` ); // test against constant implementation, with both invalid and valid signatures - equivalentProvable({ from: [oneOf(signature, badSignature)], to: bool })( + equivalentProvable({ + from: [oneOf(signature, badSignature)], + to: bool, + verbose: true, + })( ({ signature, publicKey, msg }) => { return verifyEcdsaConstant(Curve, signature, msg, publicKey); }, verify, - 'verify' + `${Curve.name}: verify` ); } From 80d10a661b198ed86d332adbadb8c3302887fdf5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:32:36 +0100 Subject: [PATCH 1014/1215] catch missing provable --- src/lib/testing/equivalent.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index b4756df727..e3e42d5551 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -6,6 +6,7 @@ import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; import { Bool, Field } from '../core.js'; import { provable } from '../circuit_value.js'; +import { assert } from '../gadgets/common.js'; export { equivalent, @@ -185,11 +186,17 @@ function equivalentAsync< // equivalence tester for provable code +function isProvable(spec: FromSpecUnion) { + return spec.specs.some((spec) => spec.provable); +} + function equivalentProvable< In extends Tuple>, Out extends ToSpec >({ from: fromRaw, to, verbose }: { from: In; to: Out; verbose?: boolean }) { let fromUnions = fromRaw.map(toUnion); + assert(fromUnions.some(isProvable), 'equivalentProvable: no provable input'); + return function run( f1: (...args: Params1) => First, f2: (...args: Params2) => Second, From d29a9b5d9d0f17a38e5421f4bb79b024f5a42ed8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 09:53:00 +0100 Subject: [PATCH 1015/1215] fix ecdsa unit test --- src/lib/gadgets/ecdsa.unit-test.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 6b47310d38..78f83e6360 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -1,6 +1,7 @@ import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; import { Ecdsa, + EllipticCurve, Point, initialAggregator, verifyEcdsaConstant, @@ -12,6 +13,7 @@ import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; import { foreignField, uniformForeignField } from './test-utils.js'; import { + First, Second, bool, equivalentProvable, @@ -53,21 +55,34 @@ for (let Curve of curves) { // provable method we want to test const verify = (s: Second) => { + // invalid public key can lead to either a failing constraint, or verify() returning false + EllipticCurve.assertOnCurve(s.publicKey, Curve); return Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); }; + // input validation equivalent to the one implicit in verify() + const checkInputs = ({ + signature: { r, s }, + publicKey, + }: First) => { + assert(r !== 0n && s !== 0n, 'invalid signature'); + let pk = Curve.fromNonzero(publicKey); + assert(Curve.isOnCurve(pk), 'invalid public key'); + return true; + }; + // positive test equivalentProvable({ from: [signature], to: bool, verbose: true })( () => true, verify, - `${Curve.name}: valid signature verifies` + `${Curve.name}: verifies` ); // negative test equivalentProvable({ from: [badSignature], to: bool, verbose: true })( - () => false, + (s) => checkInputs(s) && false, verify, - `${Curve.name}: invalid signature fails` + `${Curve.name}: fails` ); // test against constant implementation, with both invalid and valid signatures @@ -77,6 +92,7 @@ for (let Curve of curves) { verbose: true, })( ({ signature, publicKey, msg }) => { + checkInputs({ signature, publicKey, msg }); return verifyEcdsaConstant(Curve, signature, msg, publicKey); }, verify, From f36ac3d73b4a6d7103c3a60002a662011f15c50f Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 5 Dec 2023 10:19:53 +0100 Subject: [PATCH 1016/1215] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e2bcd567..7ac14ae655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes -- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1240 +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1292 [@rpanic](https://github.com/rpanic) ### Added From 09d9dd090ab24799adc3d72bfa17d8d355b1f371 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 10:23:15 +0100 Subject: [PATCH 1017/1215] trim down changelog and highlight contributor --- CHANGELOG.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de16de9095..d29aa26dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,14 +28,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. +- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. [@LuffySama-Dev](https://github.com/LuffySama-Dev) - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 - - `this.x.assertNothing()` is now `this.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1263 + - `this.account.x.getAndAssertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 - `this.account.x.assertBetween()` is now `this.account.x.requireBetween()` https://github.com/o1-labs/o1js/pull/1265 - - `this.account.x.assertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 - - `this.account.x.assertNothing()` is now `this.account.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1265 - - `this.currentSlot.assertBetween(x,y)` is now `this.currentSlot.requireBetween(x,y)` https://github.com/o1-labs/o1js/pull/1265 - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) From 40cb3740a8f355085923fc1a220fd260a3e53229 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 10:26:46 +0100 Subject: [PATCH 1018/1215] remove use of build:node --- .github/workflows/build-action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index 3801f8ea78..43cea697ff 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -39,7 +39,7 @@ jobs: run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build touch profiling.md sh run-ci-tests.sh cat profiling.md >> $GITHUB_STEP_SUMMARY @@ -91,7 +91,7 @@ jobs: run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build - name: Publish to NPM if version has changed uses: JS-DevTools/npm-publish@v1 if: github.ref == 'refs/heads/main' From ce826670531b49660ef5fabba9d135234662ae99 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 10:27:15 +0100 Subject: [PATCH 1019/1215] remove use of build:node --- .github/actions/live-tests-shared/action.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index 05d8970d97..a84bad475a 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -1,11 +1,11 @@ -name: "Shared steps for live testing jobs" -description: "Shared steps for live testing jobs" +name: 'Shared steps for live testing jobs' +description: 'Shared steps for live testing jobs' inputs: mina-branch-name: - description: "Mina branch name in use by service container" + description: 'Mina branch name in use by service container' required: true runs: - using: "composite" + using: 'composite' steps: - name: Wait for Mina network readiness uses: o1-labs/wait-for-mina-network-action@v1 @@ -16,15 +16,15 @@ runs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "20" + node-version: '20' - name: Build o1js and execute tests env: - TEST_TYPE: "Live integration tests" - USE_CUSTOM_LOCAL_NETWORK: "true" + TEST_TYPE: 'Live integration tests' + USE_CUSTOM_LOCAL_NETWORK: 'true' run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build touch profiling.md sh run-ci-tests.sh cat profiling.md >> $GITHUB_STEP_SUMMARY From 81d00472da44fb90dd09bf94f9836a97577f0032 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 10:49:13 +0100 Subject: [PATCH 1020/1215] improve comments --- src/lib/foreign-ecdsa.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 0615853ccd..ddfc08aa4e 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -58,7 +58,32 @@ class EcdsaSignature { /** * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). * - * This method proves that the signature is valid, and throws if it isn't. + * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. + * So, to actually prove validity of a signature, you need to assert that the result is true. + * + * @example + * ```ts + * // create classes for your curve + * class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} + * class Scalar extends Secp256k1.Scalar {} + * class Ecdsa extends createEcdsa(Secp256k1) {} + * + * // outside provable code: create inputs + * let privateKey = Scalar.random(); + * let publicKey = Secp256k1.generator.scale(privateKey); + * let messageHash = Scalar.random(); + * let signature = Ecdsa.sign(messageHash.toBigInt(), privateKey.toBigInt()); + * + * // ... + * // in provable code: create input witnesses (or use method inputs, or constants) + * let pk = Provable.witness(Secp256k1.provable, () => publicKey); + * let msgHash = Provable.witness(Scalar.Canonical.provable, () => messageHash); + * let sig = Provable.witness(Ecdsa.provable, () => signature); + * + * // verify signature + * let isValid = sig.verify(msgHash, pk); + * isValid.assertTrue('signature verifies'); + * ``` */ verify(msgHash: AlmostForeignField | bigint, publicKey: FlexiblePoint) { let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); @@ -73,6 +98,8 @@ class EcdsaSignature { /** * Create an {@link EcdsaSignature} by signing a message hash with a private key. + * + * Note: This method is not provable, and only takes JS bigints as input. */ static sign(msgHash: bigint, privateKey: bigint) { let { r, s } = Gadgets.Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); From 818046c90859694251cad6af1f1843d1e7cff490 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 11:25:22 +0100 Subject: [PATCH 1021/1215] flesh out doccomments --- src/index.ts | 4 +- src/lib/foreign-curve.ts | 84 ++++++++++++++++++++++++++++++++-------- src/lib/foreign-ecdsa.ts | 11 ++++-- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8d5750ef7e..bbe7d8bb74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,8 +7,8 @@ export { AlmostForeignField, CanonicalForeignField, } from './lib/foreign-field.js'; -export { createForeignCurve } from './lib/foreign-curve.js'; -export { createEcdsa } from './lib/foreign-ecdsa.js'; +export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; +export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; export * from './lib/signature.js'; export type { diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index a96f3fd8a9..fe9a11668c 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -32,11 +32,14 @@ class ForeignCurve { /** * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. + * * @example * ```ts * let x = new ForeignCurve({ x: 1n, y: 1n }); * ``` * + * **Important**: By design, there is no way for a `ForeignCurve` to represent the zero point. + * * **Warning**: This fails for a constant input which does not represent an actual point on the curve. */ constructor(g: { @@ -60,12 +63,21 @@ class ForeignCurve { return new this(g); } + /** + * The constant generator point. + */ static get generator() { return new this(this.Bigint.one); } + /** + * The size of the curve's base field. + */ static get modulus() { return this.Bigint.modulus; } + /** + * The size of the curve's base field. + */ get modulus() { return this.Constructor.Bigint.modulus; } @@ -91,6 +103,21 @@ class ForeignCurve { /** * Elliptic curve addition. + * + * **Important**: this is _incomplete addition_ and does not handle any of the degenerate cases: + * - inputs are equal (where you need to use {@link double}) + * - inputs are inverses of each other, so that the result is the zero point + * - the second input is constant and not on the curve + * + * In the case that both inputs are equal, the result of this method is garbage + * and can be manipulated arbitrarily by a malicious prover. + * + * @throws if the inputs are inverses of each other. + * + * @example + * ```ts + * let r = p.add(q); // r = p + q + * ``` */ add(h: ForeignCurve | FlexiblePoint) { let Curve = this.Constructor.Bigint; @@ -101,6 +128,11 @@ class ForeignCurve { /** * Elliptic curve doubling. + * + * @example + * ```ts + * let r = p.double(); // r = 2 * p + * ``` */ double() { let Curve = this.Constructor.Bigint; @@ -110,33 +142,47 @@ class ForeignCurve { /** * Elliptic curve negation. + * + * @example + * ```ts + * let r = p.negate(); // r = -p + * ``` */ negate(): ForeignCurve { return new this.Constructor({ x: this.x, y: this.y.neg() }); } + /** + * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. + * + * **Important**: this proves that the result of the scalar multiplication is not the zero point. + * + * @throws if the scalar multiplication results in the zero point; for example, if the scalar is zero. + * + * @example + * ```ts + * let r = p.scale(s); // r = s * p + * ``` + */ + scale(scalar: AlmostForeignField | bigint | number) { + let Curve = this.Constructor.Bigint; + let scalar_ = this.Constructor.Scalar.from(scalar); + let p = EllipticCurve.scale(scalar_.value, toPoint(this), Curve); + return new this.Constructor(p); + } + static assertOnCurve(g: ForeignCurve) { EllipticCurve.assertOnCurve(toPoint(g), this.Bigint); } /** * Assert that this point lies on the elliptic curve, which means it satisfies the equation - * y^2 = x^3 + ax + b + * `y^2 = x^3 + ax + b` */ assertOnCurve() { this.Constructor.assertOnCurve(this); } - /** - * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. - */ - scale(scalar: AlmostForeignField | bigint | number) { - let Curve = this.Constructor.Bigint; - let scalar_ = this.Constructor.Scalar.from(scalar); - let p = EllipticCurve.scale(scalar_.value, toPoint(this), Curve); - return new this.Constructor(p); - } - static assertInSubgroup(g: ForeignCurve) { if (this.Bigint.hasCofactor) { EllipticCurve.assertInSubgroup(toPoint(g), this.Bigint); @@ -144,8 +190,10 @@ class ForeignCurve { } /** - * Assert than this point lies in the subgroup defined by order*P = 0, - * by performing the scalar multiplication. + * Assert that this point lies in the subgroup defined by `order*P = 0`. + * + * Note: this is a no-op if the curve has cofactor equal to 1. Otherwise + * it performs the full scalar multiplication `order*P` and is expensive. */ assertInSubgroup() { this.Constructor.assertInSubgroup(this); @@ -213,11 +261,13 @@ class ForeignCurve { * const Curve = createForeignCurve(Crypto.CurveParams.Secp256k1); * ``` * - * `createForeignCurve(params)` takes the curve parameters {@link CurveParams} as input. - * We support `modulus` and `order` to be prime numbers to 259 bits. + * `createForeignCurve(params)` takes curve parameters {@link CurveParams} as input. + * We support `modulus` and `order` to be prime numbers up to 259 bits. + * + * The returned {@link ForeignCurve} class represents a _non-zero curve point_ and supports standard + * elliptic curve operations like point addition and scalar multiplication. * - * The returned {@link ForeignCurve} class supports standard elliptic curve operations like point addition and scalar multiplication. - * It also includes to associated foreign fields: `ForeignCurve.Field` and `ForeignCurve.Scalar`, see {@link createForeignField}. + * {@link ForeignCurve} also includes to associated foreign fields: `ForeignCurve.Field` and `ForeignCurve.Scalar`, see {@link createForeignField}. */ function createForeignCurve(params: CurveParams): typeof ForeignCurve { const FieldUnreduced = createForeignField(params.modulus); diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index ddfc08aa4e..6471831442 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -13,7 +13,7 @@ import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; // external API -export { createEcdsa }; +export { createEcdsa, EcdsaSignature }; type FlexibleSignature = | EcdsaSignature @@ -61,6 +61,8 @@ class EcdsaSignature { * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. * So, to actually prove validity of a signature, you need to assert that the result is true. * + * @throws if one of the signature scalars is zero or if the public key is not on the curve. + * * @example * ```ts * // create classes for your curve @@ -138,10 +140,11 @@ class EcdsaSignature { } /** - * Returns a class {@link EcdsaSignature} enabling to verify ECDSA signatures - * on the given curve, in provable code. + * Create a class {@link EcdsaSignature} for verifying ECDSA signatures on the given curve. */ -function createEcdsa(curve: CurveParams | typeof ForeignCurve) { +function createEcdsa( + curve: CurveParams | typeof ForeignCurve +): typeof EcdsaSignature { let Curve0: typeof ForeignCurve = 'b' in curve ? createForeignCurve(curve) : curve; class Curve extends Curve0 {} From 3d14a4edacad3864626010a02ed0c0cbdc9d126b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:14:34 +0100 Subject: [PATCH 1022/1215] move dangerous foreign field equals --- src/lib/foreign-field.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 94f660eac1..9fe18eb993 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -341,25 +341,6 @@ class ForeignField { } } - /** - * Check equality with a ForeignField-like value - * @example - * ```ts - * let isXZero = x.equals(0); - * ``` - */ - equals(y: ForeignField | bigint | number) { - const p = this.modulus; - if (this.isConstant() && isConstant(y)) { - return new Bool(this.toBigInt() === mod(toBigInt(y), p)); - } - return Provable.equal( - this.Constructor.provable, - this, - new this.Constructor(y) - ); - } - // bit packing /** @@ -542,6 +523,25 @@ class CanonicalForeignField extends ForeignFieldWithMul { static unsafeFrom(x: ForeignField) { return new this(x.value); } + + /** + * Check equality with a ForeignField-like value. + * + * @example + * ```ts + * let isXZero = x.equals(0); + * ``` + * + * Note: This method only exists on canonical fields; on unreduced fields, it would be easy to + * misuse, because not being exactly equal does not imply being unequal modulo p. + */ + equals(y: CanonicalForeignField | bigint | number) { + return Provable.equal( + this.Constructor.provable, + this, + new this.Constructor(y) + ); + } } function toLimbs( From dfc7e97251e23749c38b6494e2b882305cb91bd8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:44:14 +0100 Subject: [PATCH 1023/1215] change to safe equals() methods in foreign field, rename assertCanonical --- src/lib/foreign-field.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 9fe18eb993..ea85da47d2 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -10,6 +10,7 @@ import { Bool } from './bool.js'; import { Tuple, TupleMap, TupleN } from './util/types.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Gadgets } from './gadgets/gadgets.js'; +import { ForeignField as FF } from './gadgets/foreign-field.js'; import { assert } from './gadgets/common.js'; import { l3, l } from './gadgets/range-check.js'; import { ProvablePureExtended } from './circuit_value.js'; @@ -155,7 +156,7 @@ class ForeignField { * For a more efficient version of this for multiple field elements, see {@link assertAlmostReduced}. * * Note: this does not ensure that the field elements is in the canonical range [0, p). - * To assert that stronger property, there is {@link assertCanonicalFieldElement}. + * To assert that stronger property, there is {@link assertCanonical}. * You should typically use {@link assertAlmostReduced} though, because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ @@ -190,7 +191,7 @@ class ForeignField { * * Returns the field element as a {@link CanonicalForeignField}. */ - assertCanonicalFieldElement() { + assertCanonical() { this.assertLessThan(this.modulus); return this.Constructor.Canonical.unsafeFrom(this); } @@ -493,6 +494,18 @@ class AlmostForeignField extends ForeignFieldWithMul { static unsafeFrom(x: ForeignField) { return new this(x.value); } + + /** + * Check equality with a constant value. + * + * @example + * ```ts + * let isXZero = x.equals(0); + * ``` + */ + equals(y: bigint | number) { + return FF.equals(this.value, BigInt(y), this.modulus); + } } class CanonicalForeignField extends ForeignFieldWithMul { @@ -512,7 +525,7 @@ class CanonicalForeignField extends ForeignFieldWithMul { static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); - x.assertCanonicalFieldElement(); + x.assertCanonical(); } /** @@ -529,18 +542,18 @@ class CanonicalForeignField extends ForeignFieldWithMul { * * @example * ```ts - * let isXZero = x.equals(0); + * let isEqual = x.equals(y); * ``` * * Note: This method only exists on canonical fields; on unreduced fields, it would be easy to * misuse, because not being exactly equal does not imply being unequal modulo p. */ equals(y: CanonicalForeignField | bigint | number) { - return Provable.equal( - this.Constructor.provable, - this, - new this.Constructor(y) - ); + let [x0, x1, x2] = this.value; + let [y0, y1, y2] = toLimbs(y, this.modulus); + let x01 = x0.add(x1.mul(1n << l)).seal(); + let y01 = y0.add(y1.mul(1n << l)).seal(); + return x01.equals(y01).and(x2.equals(y2)); } } @@ -600,10 +613,10 @@ function isConstant(x: bigint | number | string | ForeignField) { * ``` * * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. - * To convert to a canonical field element, use {@link assertCanonicalFieldElement}: + * To convert to a canonical field element, use {@link assertCanonical}: * * ```ts - * x.assertCanonicalFieldElement(); // asserts x < p; returns `CanonicalForeignField` + * x.assertCanonical(); // asserts x < p; returns `CanonicalForeignField` * ``` * You will likely not need canonical fields most of the time. * From 0403912d838152c7bc39e24404be250cdeca16cf Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:44:28 +0100 Subject: [PATCH 1024/1215] add safe ec addition --- src/lib/foreign-curve.ts | 43 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index fe9a11668c..08fb733bfd 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -104,20 +104,19 @@ class ForeignCurve { /** * Elliptic curve addition. * - * **Important**: this is _incomplete addition_ and does not handle any of the degenerate cases: - * - inputs are equal (where you need to use {@link double}) - * - inputs are inverses of each other, so that the result is the zero point - * - the second input is constant and not on the curve - * - * In the case that both inputs are equal, the result of this method is garbage - * and can be manipulated arbitrarily by a malicious prover. - * - * @throws if the inputs are inverses of each other. - * - * @example * ```ts * let r = p.add(q); // r = p + q * ``` + * + * **Important**: this is _incomplete addition_ and does not handle the degenerate cases: + * - Inputs are equal, `g = h` (where you would use {@link double}). + * In this case, the result of this method is garbage and can be manipulated arbitrarily by a malicious prover. + * - Inputs are inverses of each other, `g = -h`, so that the result would be the zero point. + * In this case, the proof fails. + * + * If you want guaranteed soundness regardless of the input, use {@link addSafe} instead. + * + * @throws if the inputs are inverses of each other. */ add(h: ForeignCurve | FlexiblePoint) { let Curve = this.Constructor.Bigint; @@ -126,6 +125,28 @@ class ForeignCurve { return new this.Constructor(p); } + /** + * Safe elliptic curve addition. + * + * This is the same as {@link add}, but additionally proves that the inputs are not equal. + * Therefore, the method is guaranteed to either fail or return a valid addition result. + * + * **Beware**: this is more expensive than {@link add}, and is still incomplete in that + * it does not succeed on equal or inverse inputs. + * + * @throws if the inputs are equal or inverses of each other. + */ + addSafe(h: ForeignCurve | FlexiblePoint) { + let h_ = this.Constructor.from(h); + + // prove that we have x1 != x2 => g != +-h + let x1 = this.x.assertCanonical(); + let x2 = h_.x.assertCanonical(); + x1.equals(x2).assertFalse(); + + return this.add(h_); + } + /** * Elliptic curve doubling. * From abe4d56db51af734609a13481f2a58eade132527 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:47:36 +0100 Subject: [PATCH 1025/1215] signature to bigint --- src/lib/foreign-ecdsa.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 6471831442..43943c8533 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -55,6 +55,13 @@ class EcdsaSignature { return new this(s); } + /** + * Convert this signature to an object with bigint fields. + */ + toBigInt() { + return { r: this.r.toBigInt(), s: this.s.toBigInt() }; + } + /** * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). * From 3a3cce396ec2e675b5c8c1abad5264568c90653a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:50:13 +0100 Subject: [PATCH 1026/1215] adapt unit test --- src/lib/foreign-field.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 5686b3ef49..26f85a5699 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -84,8 +84,8 @@ equivalent({ from: [f, f], to: f })( (x, y) => x.div(y) ); -// equality -equivalent({ from: [f, f], to: bool })( +// equality with a constant +equivalent({ from: [f, first(f)], to: bool })( (x, y) => x === y, (x, y) => x.equals(y) ); From 9bcb6fd9ecbf333e5c8b6ba40883e60d2b39945e Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:51:30 +0100 Subject: [PATCH 1027/1215] delete ec and ecdsa gadgets namespaces --- src/lib/foreign-ecdsa.ts | 8 +- src/lib/gadgets/gadgets.ts | 148 ------------------------------------- 2 files changed, 4 insertions(+), 152 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 43943c8533..235e284556 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -10,7 +10,7 @@ import { import { AlmostForeignField } from './foreign-field.js'; import { assert } from './gadgets/common.js'; import { Field3 } from './gadgets/foreign-field.js'; -import { Gadgets } from './gadgets/gadgets.js'; +import { Ecdsa } from './gadgets/elliptic-curve.js'; // external API export { createEcdsa, EcdsaSignature }; @@ -51,7 +51,7 @@ class EcdsaSignature { * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). */ static fromHex(rawSignature: string): EcdsaSignature { - let s = Gadgets.Ecdsa.Signature.fromHex(rawSignature); + let s = Ecdsa.Signature.fromHex(rawSignature); return new this(s); } @@ -97,7 +97,7 @@ class EcdsaSignature { verify(msgHash: AlmostForeignField | bigint, publicKey: FlexiblePoint) { let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); let publicKey_ = this.Constructor.Curve.from(publicKey); - return Gadgets.Ecdsa.verify( + return Ecdsa.verify( this.Constructor.Curve.Bigint, toObject(this), msgHash_.value, @@ -111,7 +111,7 @@ class EcdsaSignature { * Note: This method is not provable, and only takes JS bigints as input. */ static sign(msgHash: bigint, privateKey: bigint) { - let { r, s } = Gadgets.Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); + let { r, s } = Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); return new this({ r, s }); } diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 43e5bc272f..5a6e819729 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -599,136 +599,6 @@ const Gadgets = { }, }, - /** - * Operations on elliptic curves in non-native arithmetic. - */ - EllipticCurve: { - /** - * Elliptic curve addition. - */ - add(p: Point, q: Point, Curve: { modulus: bigint }) { - return EllipticCurve.add(p, q, Curve); - }, - - /** - * Elliptic curve doubling. - */ - double(p: Point, Curve: { modulus: bigint; a: bigint }) { - return EllipticCurve.double(p, Curve); - }, - - /** - * Elliptic curve negation. - */ - negate(p: Point, Curve: { modulus: bigint }) { - return EllipticCurve.negate(p, Curve); - }, - - /** - * Scalar multiplication. - */ - scale(scalar: Field3, p: Point, Curve: CurveAffine) { - return EllipticCurve.scale(scalar, p, Curve); - }, - - /** - * Multi-scalar multiplication. - */ - scaleMany(scalars: Field3[], points: Point[], Curve: CurveAffine) { - return EllipticCurve.multiScalarMul(scalars, points, Curve); - }, - - /** - * Prove that the given point is on the given curve. - * - * @example - * ```ts - * Gadgets.ForeignCurve.assertPointOnCurve(point, Curve); - * ``` - */ - assertOnCurve(p: Point, Curve: { modulus: bigint; a: bigint; b: bigint }) { - EllipticCurve.assertOnCurve(p, Curve); - }, - - /** - * Prove that the given point is in the prime-order subgroup of the given curve. - * - * @example - * ```ts - * Gadgets.ForeignCurve.assertInSubgroup(point, Curve); - * ``` - */ - assertInSubgroup(p: Point, Curve: CurveAffine) { - EllipticCurve.assertInSubgroup(p, Curve); - }, - - /** - * Non-provabe helper methods for interacting with elliptic curves. - */ - Point, - }, - - /** - * ECDSA verification gadget and helper methods. - */ - Ecdsa: { - /** - * Verify an ECDSA signature. - * - * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. - * So, to actually prove validity of a signature, you need to assert that the result is true. - * - * @example - * ```ts - * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); - * - * // assert that message hash and signature are valid scalar field elements - * Gadgets.ForeignField.assertAlmostReduced( - * [signature.r, signature.s, msgHash], - * Curve.order - * ); - * - * // assert that the public key is valid - * Gadgets.ForeignField.assertAlmostReduced( - * [publicKey.x, publicKey.y], - * Curve.modulus - * ); - * Gadgets.EllipticCurve.assertOnCurve(publicKey, Curve); - * Gadgets.EllipticCurve.assertInSubgroup(publicKey, Curve); - * - * // verify signature - * let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); - * isValid.assertTrue(); - * ``` - */ - verify( - Curve: CurveAffine, - signature: Ecdsa.Signature, - msgHash: Field3, - publicKey: Point - ) { - return Ecdsa.verify(Curve, signature, msgHash, publicKey); - }, - - /** - * Sign a message hash using ECDSA. - * - * _This method is not provable._ - */ - sign( - Curve: Crypto.Curve, - msgHash: bigint, - privateKey: bigint - ): Ecdsa.signature { - return Ecdsa.sign(Curve, msgHash, privateKey); - }, - - /** - * Non-provable helper methods for interacting with ECDSA signatures. - */ - Signature: Ecdsa.Signature, - }, - /** * Helper methods to interact with 3-limb vectors of Fields. * @@ -749,23 +619,5 @@ export namespace Gadgets { */ export type Sum = Sum_; } - - /** - * Non-zero elliptic curve point in affine coordinates. - * - * The coordinates are represented as 3-limb bigints. - */ - export type Point = Point_; - - export namespace Ecdsa { - /** - * ECDSA signature consisting of two curve scalars. - */ - export type Signature = EcdsaSignature; - export type signature = ecdsaSignature; - } } type Sum_ = Sum; -type Point_ = Point; -type EcdsaSignature = Ecdsa.Signature; -type ecdsaSignature = Ecdsa.signature; From 94e257a11f223efb31db88abaf66b0503fb21e35 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:56:02 +0100 Subject: [PATCH 1028/1215] 0.15.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 569c4587cc..69d02ed6c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.14.3", + "version": "0.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.14.3", + "version": "0.15.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 6c3560c2d4..11493385f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.14.3", + "version": "0.15.0", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From c6022cd57db51cbc678a59405c36fb7945fa4a76 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 12:56:31 +0100 Subject: [PATCH 1029/1215] minor bumpt because of breaking changes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be68db7628..a6fc3054c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) -## [0.14.3](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) +## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) ### Breaking changes From 31282ea00f1fc37c44bbfcf9394bbe75c69fe0df Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 13:57:56 +0100 Subject: [PATCH 1030/1215] changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b10f664f9..d29f89a009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 -- **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240 - - For an example, see `./src/examples/zkprogram/ecdsa` +- Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 +- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 + - For an example, see `./src/examples/crypto/ecdsa` - `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 - `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 From ea98308fd5fe68bd85531540fe22b8986305af74 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 11:30:44 +0100 Subject: [PATCH 1031/1215] address misc feedback --- src/lib/gadgets/basic.ts | 6 +++++- src/lib/gadgets/foreign-field.ts | 5 ++++- src/lib/gadgets/gadgets.ts | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index fff909cec3..3fde1ed598 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -74,6 +74,10 @@ function assertBilinear( // b*x + c*y - z? + a*x*y + d === 0 Gates.generic( { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, - { left: x, right: y, out: z === undefined ? x : z } + { left: x, right: y, out: z === undefined ? emptyCell() : z } ); } + +function emptyCell() { + return existsOne(() => 0n); +} diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index d9b24f8dd0..f5833a6854 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -68,7 +68,7 @@ const ForeignField = { // 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 or 1) + // (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); }, }; @@ -604,6 +604,9 @@ class Sum { let overflows: Field[] = []; let xRef = Unconstrained.witness(() => Field3.toBigint(xs[0])); + // this loop mirrors the computation that a chain of ffadd gates does, + // but everything is done only on the lowest limb and using generic gates. + // the output is a sequence of low limbs (x0) and overflows, which will be wired to the ffadd results at each step. for (let i = 0; i < n; i++) { // compute carry and overflow let [carry, overflow] = exists(2, () => { diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 66ebc494ac..b8a31cb0a0 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -503,7 +503,7 @@ const Gadgets = { * Note: This is much more efficient than using {@link ForeignField.add} and {@link ForeignField.sub} separately to * compute the multiplication inputs and outputs, and then using {@link ForeignField.mul} to constrain the result. * - * The sums passed into this gadgets are "lazy sums" created with {@link ForeignField.Sum}. + * The sums passed into this method are "lazy sums" created with {@link ForeignField.Sum}. * You can also pass in plain {@link Field3} elements. * * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link ForeignField.mul}: From 74ff146b5a10da8846fc335d2e7162c7b62d9ce2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 11:38:36 +0100 Subject: [PATCH 1032/1215] add negative unit test --- src/lib/gadgets/foreign-field.unit-test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 8dc7f1eb54..a3af9587cb 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -93,6 +93,19 @@ for (let F of fields) { (x, y) => assertMulExample(x, y, F.modulus), 'assertMul' ); + // test for assertMul which mostly tests the negative case because for random inputs, we expect + // (x - y) * z != a + b + equivalentProvable({ from: [f, f, f, f, f], to: unit })( + (x, y, z, a, b) => assert(F.mul(F.sub(x, y), z) === F.add(a, b)), + (x, y, z, a, b) => + ForeignField.assertMul( + ForeignField.Sum(x).sub(y), + z, + ForeignField.Sum(a).add(b), + F.modulus + ), + 'assertMul negative' + ); // tests with inputs that aren't reduced mod f let big264 = unreducedForeignField(264, F); // this is the max size supported by our range checks / ffadd From 97013753936b89470f488c0ac64c9be11cd4b09a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 14:03:17 +0100 Subject: [PATCH 1033/1215] change name that's too long --- src/lib/foreign-field.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index d5ef7447b6..706ec50648 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -149,7 +149,7 @@ class ForeignField { * For a more efficient version of this for multiple field elements, see {@link assertAlmostReduced}. * * Note: this does not ensure that the field elements is in the canonical range [0, p). - * To assert that stronger property, there is {@link assertCanonicalFieldElement}. + * To assert that stronger property, there is {@link assertCanonical}. * You should typically use {@link assertAlmostReduced} though, because it is cheaper to prove and sufficient for * ensuring validity of all our non-native field arithmetic methods. */ @@ -186,7 +186,7 @@ class ForeignField { * * Returns the field element as a {@link CanonicalForeignField}. */ - assertCanonicalFieldElement() { + assertCanonical() { this.assertLessThan(this.modulus); return this.Constructor.Canonical.unsafeFrom(this); } @@ -514,7 +514,7 @@ class CanonicalForeignField extends ForeignFieldWithMul { static check(x: ForeignField) { Gadgets.multiRangeCheck(x.value); - x.assertCanonicalFieldElement(); + x.assertCanonical(); } /** From 237fca11821915ccd64d76ab38a89d8046d50852 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 14:17:58 +0100 Subject: [PATCH 1034/1215] fix doccomments --- src/lib/foreign-field.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 706ec50648..11e97f6262 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -583,10 +583,10 @@ function isConstant(x: bigint | number | string | ForeignField) { * ``` * * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. - * To convert to a canonical field element, use {@link assertCanonicalFieldElement}: + * To convert to a canonical field element, use {@link ForeignField.assertCanonical}: * * ```ts - * x.assertCanonicalFieldElement(); // asserts x < p; returns `CanonicalForeignField` + * x.assertCanonical(); // asserts x < p; returns `CanonicalForeignField` * ``` * You will likely not need canonical fields most of the time. * From b5b0ee444f71390d2590d3ac0fff3dc26cee3a43 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:29:31 +0000 Subject: [PATCH 1035/1215] Improved test and cleaned up implementation --- package-lock.json | 13 +++ package.json | 1 + src/lib/keccak.ts | 92 +++++++++++++++------ src/lib/keccak.unit-test.ts | 161 ++++++++++++++++++++++++++---------- 4 files changed, 196 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index eff466f3fb..a518898026 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "snarky-run": "src/build/run.js" }, "devDependencies": { + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", @@ -1486,6 +1487,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index b890fd3dba..5b6a3f46f9 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ }, "author": "O(1) Labs", "devDependencies": { + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index d01f0cfd97..c6b6db9ad3 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -5,7 +5,39 @@ import { existsOne, exists } from './gadgets/common.js'; import { TupleN } from './util/types.js'; import { rangeCheck8 } from './gadgets/range-check.js'; -export { preNist, nistSha3, ethereum }; +export { Keccak }; + +const Keccak = { + /** TODO */ + preNist( + len: number, + message: Field[], + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false + ) { + return preNist(len, message, inpEndian, outEndian, byteChecks); + }, + /** TODO */ + nistSha3( + len: number, + message: Field[], + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false + ) { + return nistSha3(len, message, inpEndian, outEndian, byteChecks); + }, + /** TODO */ + ethereum( + message: Field[], + inpEndian: 'Big' | 'Little' = 'Big', + outEndian: 'Big' | 'Little' = 'Big', + byteChecks: boolean = false + ) { + return ethereum(message, inpEndian, outEndian, byteChecks); + }, +}; // KECCAK CONSTANTS @@ -72,6 +104,9 @@ const ROUND_CONSTANTS = [ 0x8000000080008008n, ]; +// AUXILARY FUNCTIONS + +// Auxiliary function to check composition of 8 bytes into a 64-bit word function checkBytesToWord(word: Field, wordBytes: Field[]): void { let composition = wordBytes.reduce((acc, x, i) => { const shift = Field.from(2n ** BigInt(8 * i)); @@ -81,6 +116,8 @@ function checkBytesToWord(word: Field, wordBytes: Field[]): void { word.assertEquals(composition); } +// KECCAK STATE FUNCTIONS + // Return a keccak state where all lanes are equal to 0 const getKeccakStateZeros = (): Field[][] => Array.from(Array(KECCAK_DIM), (_) => Array(KECCAK_DIM).fill(Field.from(0))); @@ -105,11 +142,10 @@ function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { } } } - return state; } -// Converts a state of cvars to a list of bytes as cvars and creates constraints for it +// Converts a state of Fields to a list of bytes as Fields and creates constraints for it function keccakStateToBytes(state: Field[][]): Field[] { const stateLengthInBytes = KECCAK_STATE_LENGTH / 8; const bytestring: Field[] = Array.from( @@ -141,20 +177,21 @@ function keccakStateToBytes(state: Field[][]): Field[] { checkBytesToWord(state[x][y], word_bytes); } } - return bytestring; } +// XOR two states together and return the result function keccakStateXor(a: Field[][], b: Field[][]): Field[][] { assert( a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, - 'Invalid input1 dimensions' + 'Invalid a dimensions' ); assert( b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, - 'Invalid input2 dimensions' + 'Invalid b dimensions' ); + // Calls Gadgets.xor on each pair (x,y) of the states input1 and input2 and outputs the output Fields as a new matrix return a.map((row, rowIndex) => row.map((element, columnIndex) => Gadgets.xor(element, b[rowIndex][columnIndex], 64) @@ -181,8 +218,7 @@ function padNist(message: Field[], rate: number): Field[] { const extraBytes = bytesToPad(rate, message.length); // 0x06 0x00 ... 0x00 0x80 or 0x86 - const lastField = BigInt(2) ** BigInt(7); - const last = Field.from(lastField); + const last = Field.from(BigInt(2) ** BigInt(7)); // Create the padding vector const pad = Array(extraBytes).fill(Field.from(0)); @@ -205,8 +241,7 @@ function pad101(message: Field[], rate: number): Field[] { const extraBytes = bytesToPad(rate, message.length); // 0x01 0x00 ... 0x00 0x80 or 0x81 - const lastField = BigInt(2) ** BigInt(7); - const last = Field.from(lastField); + const last = Field.from(BigInt(2) ** BigInt(7)); // Create the padding vector const pad = Array(extraBytes).fill(Field.from(0)); @@ -354,6 +389,8 @@ function permutation(state: Field[][], rc: Field[]): Field[][] { ); } +// KECCAK SPONGE + // Absorb padded message into a keccak state with given rate and capacity function absorb( paddedMessage: Field[], @@ -363,13 +400,12 @@ function absorb( ): Field[][] { let state = getKeccakStateZeros(); - // split into blocks of rate bits - // for each block of rate bits in the padded message -> this is rate/8 bytes - const chunks = []; // (capacity / 8) zero bytes const zeros = Array(capacity / 8).fill(Field.from(0)); for (let i = 0; i < paddedMessage.length; i += rate / 8) { + // split into blocks of rate bits + // for each block of rate bits in the padded message -> this is rate/8 bytes const block = paddedMessage.slice(i, i + rate / 8); // pad the block with 0s to up to 1600 bits const paddedBlock = block.concat(zeros); @@ -385,7 +421,6 @@ function absorb( const statePerm = permutation(stateXor, rc); state = statePerm; } - return state; } @@ -396,6 +431,7 @@ function squeeze( rate: number, rc: Field[] ): Field[] { + // Copies a section of bytes in the bytestring into the output array const copy = ( bytestring: Field[], outputArray: Field[], @@ -421,6 +457,7 @@ function squeeze( const bytestring = keccakStateToBytes(state); const outputBytes = bytestring.slice(0, bytesPerSqueeze); copy(outputBytes, outputArray, 0, bytesPerSqueeze); + // for the rest of squeezes for (let i = 1; i < squeezes; i++) { // apply the permutation function to the state @@ -430,9 +467,9 @@ function squeeze( const outputBytesI = bytestringI.slice(0, bytesPerSqueeze); copy(outputBytesI, outputArray, bytesPerSqueeze * i, bytesPerSqueeze); } + // Obtain the hash selecting the first bitlength/8 bytes of the output array const hashed = outputArray.slice(0, length / 8); - return hashed; } @@ -449,7 +486,7 @@ function sponge( throw new Error('Invalid padded message length'); } - // setup cvars for round constants + // load round constants into Fields let rc = exists(24, () => TupleN.fromArray(24, ROUND_CONSTANTS)); // absorb @@ -462,12 +499,12 @@ function sponge( } // TODO(jackryanservia): Use lookup argument once issue is resolved -// Checks in the circuit that a list of cvars are at most 8 bits each +// Checks in the circuit that a list of Fields are at most 8 bits each function checkBytes(inputs: Field[]): void { inputs.map(rangeCheck8); } -// Keccak hash function with input message passed as list of Cvar bytes. +// Keccak hash function with input message passed as list of Field bytes. // The message will be parsed as follows: // - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) // - the 10*1 pad will take place after the message, until reaching the bit length rate. @@ -482,14 +519,17 @@ function hash( nistVersion: boolean ): Field[] { assert(capacity > 0, 'capacity must be positive'); - assert(capacity < KECCAK_STATE_LENGTH, 'capacity must be less than 1600'); + assert( + capacity < KECCAK_STATE_LENGTH, + 'capacity must be less than KECCAK_STATE_LENGTH' + ); assert(length > 0, 'length must be positive'); assert(length % 8 === 0, 'length must be a multiple of 8'); - // Set input to Big Endian format + // Input endianness conversion let messageFormatted = inpEndian === 'Big' ? message : message.reverse(); - // Check each cvar input is 8 bits at most if it was not done before at creation time + // Check each Field input is 8 bits at most if it was not done before at creation time if (byteChecks) { checkBytes(messageFormatted); } @@ -505,19 +545,17 @@ function hash( const hash = sponge(padded, length, capacity, rate); - // Check each cvar output is 8 bits at most. Always because they are created here + // Always check each Field output is 8 bits at most because they are created here checkBytes(hash); // Set input to desired endianness const hashFormatted = outEndian === 'Big' ? hash : hash.reverse(); - // Check each cvar output is 8 bits at most return hashFormatted; } // Gadget for NIST SHA-3 function for output lengths 224/256/384/512. // Input and output endianness can be specified. Default is big endian. -// Note that when calling with output length 256 this is equivalent to the ethereum function function nistSha3( len: number, message: Field[], @@ -550,10 +588,10 @@ function nistSha3( // Gadget for Keccak hash function for the parameters used in Ethereum. // Input and output endianness can be specified. Default is big endian. function ethereum( + message: Field[] = [], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', - byteChecks: boolean = false, - message: Field[] = [] + byteChecks: boolean = false ): Field[] { return hash(inpEndian, outEndian, byteChecks, message, 256, 512, false); } @@ -572,7 +610,7 @@ function preNist( case 224: return hash(inpEndian, outEndian, byteChecks, message, 224, 448, false); case 256: - return ethereum(inpEndian, outEndian, byteChecks, message); + return ethereum(message, inpEndian, outEndian, byteChecks); case 384: return hash(inpEndian, outEndian, byteChecks, message, 384, 768, false); case 512: diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index cd477ddb5c..e72e655093 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,63 +1,136 @@ import { Field } from './field.js'; import { Provable } from './provable.js'; -import { preNist, nistSha3, ethereum } from './keccak.js'; -import { sha3_256, keccak_256 } from '@noble/hashes/sha3'; +import { Keccak } from './keccak.js'; +import { keccak_256, sha3_256, keccak_512, sha3_512 } from '@noble/hashes/sha3'; import { ZkProgram } from './proof_system.js'; +import { Random } from './testing/random.js'; +import { array, equivalentAsync, fieldWithRng } from './testing/equivalent.js'; +import { constraintSystem, contains } from './testing/constraint-system.js'; +const PREIMAGE_LENGTH = 75; +const RUNS = 1; -let Keccak = ZkProgram({ - name: 'keccak', - publicInput: Provable.Array(Field, 100), +let uint = (length: number) => fieldWithRng(Random.biguint(length)); + +let Keccak256 = ZkProgram({ + name: 'keccak256', + publicInput: Provable.Array(Field, PREIMAGE_LENGTH), publicOutput: Provable.Array(Field, 32), methods: { - preNist: { + ethereum: { privateInputs: [], method(preImage) { - return preNist(256, preImage); + return Keccak.ethereum(preImage); }, }, + // No need for preNist Keccak_256 because it's identical to ethereum nistSha3: { privateInputs: [], method(preImage) { - return nistSha3(256, preImage); - }, - }, - ethereum: { - privateInputs: [], - method(preImage) { - return ethereum(preImage); + return Keccak.nistSha3(256, preImage); }, }, }, }); -console.log("compiling keccak"); -await Keccak.compile(); -console.log("done compiling keccak"); - -const runs = 2; - -let preImage = [ - 236, 185, 24, 61, 138, 249, 61, 13, 226, 103, 152, 232, 104, 234, 170, 26, - 46, 54, 157, 146, 17, 240, 10, 193, 214, 110, 134, 47, 97, 241, 172, 198, - 80, 95, 136, 185, 62, 156, 246, 210, 207, 129, 93, 162, 215, 77, 3, 38, - 194, 86, 75, 100, 64, 87, 6, 18, 4, 159, 235, 53, 87, 124, 216, 241, 179, - 201, 111, 168, 72, 181, 28, 65, 142, 243, 224, 69, 58, 178, 114, 3, 112, - 23, 15, 208, 103, 231, 114, 64, 89, 172, 240, 81, 27, 215, 129, 3, 16, - 173, 133, 160, -] - -let preNistProof = await Keccak.preNist(preImage.map(Field.from)); -console.log(preNistProof.publicOutput.toString()); -console.log(keccak_256(new Uint8Array(preImage))); -let nistSha3Proof = await Keccak.nistSha3(preImage.map(Field.from)); -console.log(nistSha3Proof.publicOutput.toString()); -console.log(sha3_256(new Uint8Array(preImage))); -let ethereumProof = await Keccak.ethereum(preImage.map(Field.from)); -console.log(ethereumProof.publicOutput.toString()); - -console.log('verifying'); -preNistProof.verify(); -nistSha3Proof.verify(); -ethereumProof.verify(); -console.log('done verifying'); +await Keccak256.compile(); + +await equivalentAsync( + { + from: [array(uint(8), PREIMAGE_LENGTH)], + to: array(uint(8), 32), + }, + { runs: RUNS } +)( + (x) => { + let uint8Array = new Uint8Array(x.map(Number)); + let result = keccak_256(uint8Array); + return Array.from(result).map(BigInt); + }, + async (x) => { + let proof = await Keccak256.ethereum(x); + return proof.publicOutput; + } +); + +await equivalentAsync( + { + from: [array(uint(8), PREIMAGE_LENGTH)], + to: array(uint(8), 32), + }, + { runs: RUNS } +)( + (x) => { + let thing = x.map(Number); + let result = sha3_256(new Uint8Array(thing)); + return Array.from(result).map(BigInt); + }, + async (x) => { + let proof = await Keccak256.nistSha3(x); + return proof.publicOutput; + } +); + +// let Keccak512 = ZkProgram({ +// name: 'keccak512', +// publicInput: Provable.Array(Field, PREIMAGE_LENGTH), +// publicOutput: Provable.Array(Field, 64), +// methods: { +// preNist: { +// privateInputs: [], +// method(preImage) { +// return Keccak.preNist(512, preImage, 'Big', 'Big', true); +// }, +// }, +// nistSha3: { +// privateInputs: [], +// method(preImage) { +// return Keccak.nistSha3(512, preImage, 'Big', 'Big', true); +// }, +// }, +// }, +// }); + +// await Keccak512.compile(); + +// await equivalentAsync( +// { +// from: [array(uint(8), PREIMAGE_LENGTH)], +// to: array(uint(8), 64), +// }, +// { runs: RUNS } +// )( +// (x) => { +// let uint8Array = new Uint8Array(x.map(Number)); +// let result = keccak_512(uint8Array); +// return Array.from(result).map(BigInt); +// }, +// async (x) => { +// let proof = await Keccak512.preNist(x); +// return proof.publicOutput; +// } +// ); + +// await equivalentAsync( +// { +// from: [array(uint(8), PREIMAGE_LENGTH)], +// to: array(uint(8), 64), +// }, +// { runs: RUNS } +// )( +// (x) => { +// let thing = x.map(Number); +// let result = sha3_512(new Uint8Array(thing)); +// return Array.from(result).map(BigInt); +// }, +// async (x) => { +// let proof = await Keccak512.nistSha3(x); +// return proof.publicOutput; +// } +// ); + +constraintSystem.fromZkProgram( + Keccak256, + 'ethereum', + contains([['Generic'], ['Xor16'], ['Zero'], ['Rot64'], ['RangeCheck0']]) +); From 27d93208b6e69fa3a74de6f56eca60b97d393669 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 6 Dec 2023 19:29:27 +0100 Subject: [PATCH 1036/1215] demo actions/reducer storage contract --- src/examples/zkapps/reducer/map.ts | 179 +++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/examples/zkapps/reducer/map.ts diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts new file mode 100644 index 0000000000..c212465134 --- /dev/null +++ b/src/examples/zkapps/reducer/map.ts @@ -0,0 +1,179 @@ +import { + Field, + Struct, + method, + PrivateKey, + SmartContract, + Mina, + AccountUpdate, + Reducer, + provable, + PublicKey, + Bool, + Poseidon, + Provable, +} from 'o1js'; + +class Option extends Struct({ + isSome: Bool, + value: Field, +}) {} + +const KeyValuePair = provable({ + key: Field, + value: Field, +}); + +class StorageContract extends SmartContract { + reducer = Reducer({ + actionType: KeyValuePair, + }); + + @method set(key: PublicKey, value: Field) { + this.reducer.dispatch({ key: Poseidon.hash(key.toFields()), value }); + } + + @method get(key: PublicKey): Option { + let pendingActions = this.reducer.getActions({ + fromActionState: Reducer.initialActionState, + }); + + let keyHash = Poseidon.hash(key.toFields()); + + let { state: optionValue } = this.reducer.reduce( + pendingActions, + Option, + ( + _state: Option, + _action: { + key: Field; + value: Field; + } + ) => { + let currentMatch = keyHash.equals(_action.key); + return { + isSome: currentMatch.or(_state.isSome), + value: Provable.if(currentMatch, _action.value, _state.value), + }; + }, + { + state: Option.empty(), + actionState: Reducer.initialActionState, + }, + { maxTransactionsWithActions: k } + ); + + return optionValue; + } +} + +let k = 1 << 4; + +let Local = Mina.LocalBlockchain(); +Mina.setActiveInstance(Local); +let cs = StorageContract.analyzeMethods(); + +console.log(`method size for a "mapping" contract with ${k} entries`); +console.log('get rows:', cs['get'].rows); +console.log('set rows:', cs['set'].rows); + +// a test account that pays all the fees, and puts additional funds into the zkapp +let feePayerKey = Local.testAccounts[0].privateKey; +let feePayer = Local.testAccounts[0].publicKey; + +// the zkapp account +let zkappKey = PrivateKey.fromBase58( + 'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD' +); +let zkappAddress = zkappKey.toPublicKey(); +let zkapp = new StorageContract(zkappAddress); + +await StorageContract.compile(); + +let tx = await Mina.transaction(feePayer, () => { + AccountUpdate.fundNewAccount(feePayer); + zkapp.deploy(); +}); +await tx.sign([feePayerKey, zkappKey]).send(); + +console.log('deployed'); + +let map: { key: PublicKey; value: Field }[] = []; +map[0] = { + key: PrivateKey.random().toPublicKey(), + value: Field(192), +}; +map[1] = { + key: PrivateKey.random().toPublicKey(), + value: Field(151), +}; +map[2] = { + key: PrivateKey.random().toPublicKey(), + value: Field(781), +}; + +let key = map[0].key; +let value = map[0].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[1].key; +value = map[1].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[2].key; +value = map[2].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[0].key; +value = map[0].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +let result: any; +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +key = map[1].key; +value = map[1].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +console.log(`getting key invalid key`); +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(PrivateKey.random().toPublicKey()); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('should be isSome(false)', result.isSome.toBoolean()); From 73dfdc540fbb2dc8c75134789e26315f8108401c Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 6 Dec 2023 19:31:27 +0100 Subject: [PATCH 1037/1215] add comment --- src/examples/zkapps/reducer/map.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts index c212465134..176a277732 100644 --- a/src/examples/zkapps/reducer/map.ts +++ b/src/examples/zkapps/reducer/map.ts @@ -14,6 +14,27 @@ import { Provable, } from 'o1js'; +/* + +This contract emulates a "mapping" data structure, which is a key-value store, similar to a dictionary or hash table or `new Map()` in JavaScript. +In this example, the keys are public keys, and the values are arbitrary field elements. + +This utilizes the `Reducer` as an append online list of actions, which are then looked at to find the value corresponding to a specific key. + + +```ts +// js +const map = new Map(); +map.set(key, value); +map.get(key); + +// contract +zkApp.deploy(); // ... deploy the zkapp +zkApp.set(key, value); // ... set a key-value pair +zkApp.get(key); // ... get a value by key +``` +*/ + class Option extends Struct({ isSome: Bool, value: Field, From f51e5c057152087529c97e6367da22422ee85c53 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:28:03 +0100 Subject: [PATCH 1038/1215] remove gadgets.ecdsa which ended up in a different final form --- src/examples/zkprogram/ecdsa/ecdsa.ts | 41 ---------------- src/examples/zkprogram/ecdsa/run.ts | 43 ----------------- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/gadgets.ts | 67 --------------------------- 4 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 src/examples/zkprogram/ecdsa/ecdsa.ts delete mode 100644 src/examples/zkprogram/ecdsa/run.ts diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts deleted file mode 100644 index 810cb33e55..0000000000 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Gadgets, ZkProgram, Struct, Crypto } from 'o1js'; - -export { ecdsaProgram, Point, Secp256k1 }; - -let { ForeignField, Field3, Ecdsa } = Gadgets; - -// TODO expose this as part of Gadgets.Curve - -class Point extends Struct({ x: Field3.provable, y: Field3.provable }) { - // point from bigints - static from({ x, y }: { x: bigint; y: bigint }) { - return new Point({ x: Field3.from(x), y: Field3.from(y) }); - } -} - -const Secp256k1 = Crypto.createCurve(Crypto.CurveParams.Secp256k1); - -const ecdsaProgram = ZkProgram({ - name: 'ecdsa', - publicInput: Point, - - methods: { - verifyEcdsa: { - privateInputs: [Ecdsa.Signature.provable, Field3.provable], - method( - publicKey: Point, - signature: Gadgets.Ecdsa.Signature, - msgHash: Gadgets.Field3 - ) { - // assert that private inputs are valid - ForeignField.assertAlmostReduced( - [signature.r, signature.s, msgHash], - Secp256k1.order - ); - - // verify signature - Ecdsa.verify(Secp256k1, signature, msgHash, publicKey); - }, - }, - }, -}); diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts deleted file mode 100644 index b1d502c7e9..0000000000 --- a/src/examples/zkprogram/ecdsa/run.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Gadgets } from 'o1js'; -import { Point, Secp256k1, ecdsaProgram } from './ecdsa.js'; -import assert from 'assert'; - -// create an example ecdsa signature - -let privateKey = Secp256k1.Scalar.random(); -let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); - -// TODO use an actual keccak hash -let messageHash = Secp256k1.Scalar.random(); - -let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); - -// investigate the constraint system generated by ECDSA verify - -console.time('ecdsa verify (build constraint system)'); -let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; -console.timeEnd('ecdsa verify (build constraint system)'); - -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} -console.log(gateTypes); - -// compile and prove - -console.time('ecdsa verify (compile)'); -await ecdsaProgram.compile(); -console.timeEnd('ecdsa verify (compile)'); - -console.time('ecdsa verify (prove)'); -let proof = await ecdsaProgram.verifyEcdsa( - Point.from(publicKey), - Gadgets.Ecdsa.Signature.from(signature), - Gadgets.Field3.from(messageHash) -); -console.timeEnd('ecdsa verify (prove)'); - -assert(await ecdsaProgram.verify(proof), 'proof verifies'); diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index 33c1f8ce6f..b8f28764b1 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -10,7 +10,7 @@ import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; -import { foreignField, throwError, uniformForeignField } from './test-utils.js'; +import { foreignField, uniformForeignField } from './test-utils.js'; import { Second, bool, diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index bc5a64fee4..038646d03d 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -10,9 +10,6 @@ import { import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; import { Field } from '../field.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; -import { Ecdsa, Point } from './elliptic-curve.js'; -import { CurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { Crypto } from '../crypto.js'; export { Gadgets }; @@ -599,60 +596,6 @@ const Gadgets = { }, }, - /** - * ECDSA verification gadget and helper methods. - */ - Ecdsa: { - // TODO add an easy way to prove that the public key lies on the curve, and show in the example - /** - * Verify an ECDSA signature. - * - * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. - * So, to actually prove validity of a signature, you need to assert that the result is true. - * - * @example - * ```ts - * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); - * - * // assert that message hash and signature are valid scalar field elements - * Gadgets.ForeignField.assertAlmostReduced( - * [signature.r, signature.s, msgHash], - * Curve.order - * ); - * - * // verify signature - * let isValid = Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); - * isValid.assertTrue(); - * ``` - */ - verify( - Curve: CurveAffine, - signature: Ecdsa.Signature, - msgHash: Field3, - publicKey: Point - ) { - Ecdsa.verify(Curve, signature, msgHash, publicKey); - }, - - /** - * Sign a message hash using ECDSA. - * - * _This method is not provable._ - */ - sign( - Curve: Crypto.Curve, - msgHash: bigint, - privateKey: bigint - ): Ecdsa.signature { - return Ecdsa.sign(Curve, msgHash, privateKey); - }, - - /** - * Non-provable helper methods for interacting with ECDSA signatures. - */ - Signature: Ecdsa.Signature, - }, - /** * Helper methods to interact with 3-limb vectors of Fields. * @@ -673,15 +616,5 @@ export namespace Gadgets { */ export type Sum = Sum_; } - - export namespace Ecdsa { - /** - * ECDSA signature consisting of two curve scalars. - */ - export type Signature = EcdsaSignature; - export type signature = ecdsaSignature; - } } type Sum_ = Sum; -type EcdsaSignature = Ecdsa.Signature; -type ecdsaSignature = Ecdsa.signature; From bbcfb17e2c5bd977b2bb2251ad838ed089d9f31e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:46:35 +0100 Subject: [PATCH 1039/1215] fix build:examples while we're here --- package.json | 2 +- src/build/copy-to-dist.js | 6 +++++- src/examples/api_exploration.ts | 2 +- src/examples/zkapps/local_events_zkapp.ts | 2 +- src/examples/zkapps/sudoku/sudoku.ts | 2 +- src/examples/zkapps/voting/member.ts | 4 ++-- src/examples/zkprogram/program-with-input.ts | 8 ++++---- src/examples/zkprogram/program.ts | 8 ++++---- src/lib/account_update.ts | 3 +++ 9 files changed, 22 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index b890fd3dba..c2b9510c5a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", - "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", + "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", diff --git a/src/build/copy-to-dist.js b/src/build/copy-to-dist.js index 96bc96937b..6218414713 100644 --- a/src/build/copy-to-dist.js +++ b/src/build/copy-to-dist.js @@ -2,7 +2,11 @@ import { copyFromTo } from './utils.js'; await copyFromTo( - ['src/snarky.d.ts', 'src/bindings/compiled/_node_bindings'], + [ + 'src/snarky.d.ts', + 'src/bindings/compiled/_node_bindings', + 'src/bindings/compiled/node_bindings/plonk_wasm.d.cts', + ], 'src/', 'dist/node/' ); diff --git a/src/examples/api_exploration.ts b/src/examples/api_exploration.ts index a797656d2c..69e8f2db3b 100644 --- a/src/examples/api_exploration.ts +++ b/src/examples/api_exploration.ts @@ -149,7 +149,7 @@ console.assert(!signature.verify(pubKey, msg1).toBoolean()); */ /* You can initialize elements as literals as follows: */ -let g0 = new Group(-1, 2); +let g0 = Group.from(-1, 2); let g1 = new Group({ x: -2, y: 2 }); /* There is also a predefined generator. */ diff --git a/src/examples/zkapps/local_events_zkapp.ts b/src/examples/zkapps/local_events_zkapp.ts index 5608625689..993ed31e1a 100644 --- a/src/examples/zkapps/local_events_zkapp.ts +++ b/src/examples/zkapps/local_events_zkapp.ts @@ -91,7 +91,7 @@ let events = await zkapp.fetchEvents(UInt32.from(0)); console.log(events); console.log('---- emitted events: ----'); // fetches all events from zkapp starting block height 0 and ending at block height 10 -events = await zkapp.fetchEvents(UInt32.from(0), UInt64.from(10)); +events = await zkapp.fetchEvents(UInt32.from(0), UInt32.from(10)); console.log(events); console.log('---- emitted events: ----'); // fetches all events diff --git a/src/examples/zkapps/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts index 5175d011a1..9af8952ecf 100644 --- a/src/examples/zkapps/sudoku/sudoku.ts +++ b/src/examples/zkapps/sudoku/sudoku.ts @@ -8,7 +8,7 @@ import { isReady, Poseidon, Struct, - Circuit, + Provable, } from 'o1js'; export { Sudoku, SudokuZkApp }; diff --git a/src/examples/zkapps/voting/member.ts b/src/examples/zkapps/voting/member.ts index 1618914b3c..c4b0f6e6cd 100644 --- a/src/examples/zkapps/voting/member.ts +++ b/src/examples/zkapps/voting/member.ts @@ -52,8 +52,8 @@ export class Member extends CircuitValue { return this; } - static empty() { - return new Member(PublicKey.empty(), UInt64.zero); + static empty any>(): InstanceType { + return new Member(PublicKey.empty(), UInt64.zero) as any; } static from(publicKey: PublicKey, balance: UInt64) { diff --git a/src/examples/zkprogram/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts index 005cce1b14..83265082a2 100644 --- a/src/examples/zkprogram/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -42,7 +42,7 @@ console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(Field(0)); @@ -52,7 +52,7 @@ proof = testJsonRoundtrip(MyProof, proof); proof satisfies Proof; console.log('verify...'); -let ok = await verify(proof.toJSON(), verificationKey); +let ok = await verify(proof.toJSON(), verificationKey.data); console.log('ok?', ok); console.log('verify alternative...'); @@ -64,7 +64,7 @@ proof = await MyProgram.inductiveCase(Field(1), proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof, verificationKey); +ok = await verify(proof, verificationKey.data); console.log('ok?', ok); console.log('verify alternative...'); @@ -76,7 +76,7 @@ proof = await MyProgram.inductiveCase(Field(2), proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey); +ok = await verify(proof.toJSON(), verificationKey.data); console.log('ok?', ok && proof.publicInput.toString() === '2'); diff --git a/src/examples/zkprogram/program.ts b/src/examples/zkprogram/program.ts index 40b5263854..0b75880b04 100644 --- a/src/examples/zkprogram/program.ts +++ b/src/examples/zkprogram/program.ts @@ -43,7 +43,7 @@ console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(); @@ -53,7 +53,7 @@ proof = testJsonRoundtrip(MyProof, proof); proof satisfies Proof; console.log('verify...'); -let ok = await verify(proof.toJSON(), verificationKey); +let ok = await verify(proof.toJSON(), verificationKey.data); console.log('ok?', ok); console.log('verify alternative...'); @@ -65,7 +65,7 @@ proof = await MyProgram.inductiveCase(proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof, verificationKey); +ok = await verify(proof, verificationKey.data); console.log('ok?', ok); console.log('verify alternative...'); @@ -77,7 +77,7 @@ proof = await MyProgram.inductiveCase(proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey); +ok = await verify(proof.toJSON(), verificationKey.data); console.log('ok?', ok && proof.publicOutput.toString() === '2'); diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c1938bd5e8..2ab0c5d50d 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1277,6 +1277,9 @@ class AccountUpdate implements Types.AccountUpdate { return [{ lazyAuthorization, children, parent, id, label }, aux]; } static toInput = Types.AccountUpdate.toInput; + static empty() { + return AccountUpdate.dummy(); + } static check = Types.AccountUpdate.check; static fromFields(fields: Field[], [other, aux]: any[]): AccountUpdate { let accountUpdate = Types.AccountUpdate.fromFields(fields, aux); From 7ec8090f57a8b3ec71915516936961f58c77f0a6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:52:42 +0100 Subject: [PATCH 1040/1215] remove ecdsa from vk test for now --- tests/vk-regression/vk-regression.json | 13 ------------- tests/vk-regression/vk-regression.ts | 2 -- 2 files changed, 15 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 0297b30a04..f60540c9f8 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -201,18 +201,5 @@ "data": "", "hash": "" } - }, - "ecdsa": { - "digest": "6e4078c6df944db119dc1656eb68e6c272c3f2e9cab6746913759537cbfcfa9", - "methods": { - "verifyEcdsa": { - "rows": 38846, - "digest": "892b0a1fad0f13d92ba6099cd54e6780" - } - }, - "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJy9KmK2C+3hCZoGpDDhYsWLlly6udS3Uh6qYr0X1NU/Ns8UpTCq9fR7ST8OmK+DdktHHPQGmGD2FHfSOPBhRicgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AggZ9tT5WqF5mKwaoycspge8k5SYrr9T1DwdfhQHP86zGDZzvZlQGyPIvrcZ+bpTMc5+4GUl8mI5/IIZnX0cM0HV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "21152043351405736424630704375244880683906889913335338686836578165782029001213" - } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index a051f21137..28a95542bd 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,7 +3,6 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; -import { ecdsaProgram } from '../../src/examples/zkprogram/ecdsa/ecdsa.js'; import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions @@ -39,7 +38,6 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ createDex().Dex, GroupCS, BitwiseCS, - ecdsaProgram, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; From db8f63afd45ae80d3e8eaa3eb9bd21d38ff17766 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:53:21 +0100 Subject: [PATCH 1041/1215] Revert "remove ecdsa from vk test for now" This reverts commit 7ec8090f57a8b3ec71915516936961f58c77f0a6. --- tests/vk-regression/vk-regression.json | 13 +++++++++++++ tests/vk-regression/vk-regression.ts | 2 ++ 2 files changed, 15 insertions(+) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index f60540c9f8..0297b30a04 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -201,5 +201,18 @@ "data": "", "hash": "" } + }, + "ecdsa": { + "digest": "6e4078c6df944db119dc1656eb68e6c272c3f2e9cab6746913759537cbfcfa9", + "methods": { + "verifyEcdsa": { + "rows": 38846, + "digest": "892b0a1fad0f13d92ba6099cd54e6780" + } + }, + "verificationKey": { + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAJy9KmK2C+3hCZoGpDDhYsWLlly6udS3Uh6qYr0X1NU/Ns8UpTCq9fR7ST8OmK+DdktHHPQGmGD2FHfSOPBhRicgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AggZ9tT5WqF5mKwaoycspge8k5SYrr9T1DwdfhQHP86zGDZzvZlQGyPIvrcZ+bpTMc5+4GUl8mI5/IIZnX0cM0HV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "21152043351405736424630704375244880683906889913335338686836578165782029001213" + } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 28a95542bd..a051f21137 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,6 +3,7 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; +import { ecdsaProgram } from '../../src/examples/zkprogram/ecdsa/ecdsa.js'; import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions @@ -38,6 +39,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ createDex().Dex, GroupCS, BitwiseCS, + ecdsaProgram, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; From 507a04be0b23ff3bb918536d328e37c0fa37fe64 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:58:21 +0100 Subject: [PATCH 1042/1215] fixup --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c2b9510c5a..8d37f38823 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", - "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", + "build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", From 62091b8025ace31d7f001f21bbba7499b75d65ab Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 7 Dec 2023 08:12:18 +0100 Subject: [PATCH 1043/1215] 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 988a1bc731..ee63f91f3a 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -203,16 +203,16 @@ } }, "ecdsa": { - "digest": "36c90ec17088e536562b06f333be6fc1bed252b47a3cfb4e4c24aa22e7a1247b", + "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", "methods": { "verifyEcdsa": { "rows": 38888, - "digest": "70f148db469f028f1d8cb99e8e8e278a" + "digest": "f75dd9e49c88eb6097a7f3abbe543467" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAP8r6+oM655IWrhl71ElRkB6sgc6QcECWuPagIo3jpwP7Up1gE0toPdVWzkPhwnFfmujzGZV30q1C/BVIM9icQcgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09AgD6ZSfWTUrxRxm0kAJzNwO5p4hhA1FjRzX3p0diEhpjyyb6kXOYxmXiAGan/A9/KoYBCxLwtJpn4CgyRa0C73EV46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "5175094130394897519519312310597261032080672241911883655552182168739967574124" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAHV05f+qac/ZBDmwqzaprv0J0hiho1m+s3yNkKQVSOkFyy3T9xnMBFjK62dF1KOp2k1Uvadd2KRyqwXiGN7JtQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09Ag9D9DKKoexOqr3N/Z3GGptvh3qvOPyxcWf475b+B/fTIwTQQC8ykkZ35HAVW3ZT6XDz0QFSmB0NJ8A+lkaTa0JF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" } } } \ No newline at end of file From 991008b8e56440bc28172223ac1a7424c108f0f0 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:50:51 +0000 Subject: [PATCH 1044/1215] Cleaned up and made variable names consistent --- src/lib/keccak.ts | 225 +++++++++++++++++------------------- src/lib/keccak.unit-test.ts | 33 +++--- 2 files changed, 122 insertions(+), 136 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index c6b6db9ad3..813b53d576 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -9,33 +9,33 @@ export { Keccak }; const Keccak = { /** TODO */ - preNist( + nistSha3( len: number, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false - ) { - return preNist(len, message, inpEndian, outEndian, byteChecks); + ): Field[] { + return nistSha3(len, message, inpEndian, outEndian, byteChecks); }, /** TODO */ - nistSha3( - len: number, + ethereum( message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false - ) { - return nistSha3(len, message, inpEndian, outEndian, byteChecks); + ): Field[] { + return ethereum(message, inpEndian, outEndian, byteChecks); }, /** TODO */ - ethereum( + preNist( + len: number, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false - ) { - return ethereum(message, inpEndian, outEndian, byteChecks); + ): Field[] { + return preNist(len, message, inpEndian, outEndian, byteChecks); }, }; @@ -60,7 +60,7 @@ const KECCAK_STATE_LENGTH = KECCAK_DIM ** 2 * KECCAK_WORD; const KECCAK_ROUNDS = 12 + 2 * KECCAK_ELL; // Creates the 5x5 table of rotation offset for Keccak modulo 64 -// | x \ y | 0 | 1 | 2 | 3 | 4 | +// | i \ j | 0 | 1 | 2 | 3 | 4 | // | ----- | -- | -- | -- | -- | -- | // | 0 | 0 | 36 | 3 | 41 | 18 | // | 1 | 1 | 44 | 10 | 45 | 2 | @@ -106,13 +106,14 @@ const ROUND_CONSTANTS = [ // AUXILARY FUNCTIONS -// Auxiliary function to check composition of 8 bytes into a 64-bit word -function checkBytesToWord(word: Field, wordBytes: Field[]): void { - let composition = wordBytes.reduce((acc, x, i) => { - const shift = Field.from(2n ** BigInt(8 * i)); - return acc.add(x.mul(shift)); +// Auxiliary function to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it +function checkBytesToWord(wordBytes: Field[], word: Field): void { + const composition = wordBytes.reduce((acc, value, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(value.mul(shift)); }, Field.from(0)); + // Create constraints to check that the word is composed correctly from bytes word.assertEquals(composition); } @@ -124,21 +125,24 @@ const getKeccakStateZeros = (): Field[][] => // Converts a list of bytes to a matrix of Field elements function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { - assert(bytestring.length === 200, 'improper bytestring length'); + assert( + bytestring.length === 200, + 'improper bytestring length (should be 200)' + ); const bytestringArray = Array.from(bytestring); const state: Field[][] = getKeccakStateZeros(); - for (let y = 0; y < KECCAK_DIM; y++) { - for (let x = 0; x < KECCAK_DIM; x++) { - const idx = BYTES_PER_WORD * (KECCAK_DIM * y + x); - // Create an array containing the 8 bytes starting on idx that correspond to the word in [x,y] + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + const idx = BYTES_PER_WORD * (KECCAK_DIM * j + i); + // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] const wordBytes = bytestringArray.slice(idx, idx + BYTES_PER_WORD); - for (let z = 0; z < BYTES_PER_WORD; z++) { - // Field element containing value 2^(8*z) - const shift = Field.from(2n ** BigInt(8 * z)); - state[x][y] = state[x][y].add(shift.mul(wordBytes[z])); + for (let k = 0; k < BYTES_PER_WORD; k++) { + // Field element containing value 2^(8*k) + const shift = 1n << BigInt(8 * k); + state[i][j] = state[i][j].add(wordBytes[k].mul(shift)); } } } @@ -152,29 +156,24 @@ function keccakStateToBytes(state: Field[][]): Field[] { { length: stateLengthInBytes }, (_, idx) => existsOne(() => { - // idx = z + 8 * ((dim * y) + x) - const z = idx % BYTES_PER_WORD; - const x = Math.floor(idx / BYTES_PER_WORD) % KECCAK_DIM; - const y = Math.floor(idx / BYTES_PER_WORD / KECCAK_DIM); - // [7 6 5 4 3 2 1 0] [x=0,y=1] [x=0,y=2] [x=0,y=3] [x=0,y=4] - // [x=1,y=0] [x=1,y=1] [x=1,y=2] [x=1,y=3] [x=1,y=4] - // [x=2,y=0] [x=2,y=1] [x=2,y=2] [x=2,y=3] [x=2,y=4] - // [x=3,y=0] [x=3,y=1] [x=3,y=2] [x=3,y=3] [x=3,y=4] - // [x=4,y=0] [x=4,y=1] [x=4,y=0] [x=4,y=3] [x=4,y=4] - const word = state[x][y].toBigInt(); - const byte = (word >> BigInt(8 * z)) & BigInt('0xff'); + // idx = k + 8 * ((dim * j) + i) + const i = Math.floor(idx / BYTES_PER_WORD) % KECCAK_DIM; + const j = Math.floor(idx / BYTES_PER_WORD / KECCAK_DIM); + const k = idx % BYTES_PER_WORD; + const word = state[i][j].toBigInt(); + const byte = (word >> BigInt(8 * k)) & 0xffn; return byte; }) ); // Check all words are composed correctly from bytes - for (let y = 0; y < KECCAK_DIM; y++) { - for (let x = 0; x < KECCAK_DIM; x++) { - const idx = BYTES_PER_WORD * (KECCAK_DIM * y + x); - // Create an array containing the 8 bytes starting on idx that correspond to the word in [x,y] + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + const idx = BYTES_PER_WORD * (KECCAK_DIM * j + i); + // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] const word_bytes = bytestring.slice(idx, idx + BYTES_PER_WORD); // Assert correct decomposition of bytes from state - checkBytesToWord(state[x][y], word_bytes); + checkBytesToWord(word_bytes, state[i][j]); } } return bytestring; @@ -184,18 +183,16 @@ function keccakStateToBytes(state: Field[][]): Field[] { function keccakStateXor(a: Field[][], b: Field[][]): Field[][] { assert( a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, - 'Invalid a dimensions' + `invalid \`a\` dimensions (should be ${KECCAK_DIM})` ); assert( b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, - 'Invalid b dimensions' + `invalid \`b\` dimensions (should be ${KECCAK_DIM})` ); - // Calls Gadgets.xor on each pair (x,y) of the states input1 and input2 and outputs the output Fields as a new matrix - return a.map((row, rowIndex) => - row.map((element, columnIndex) => - Gadgets.xor(element, b[rowIndex][columnIndex], 64) - ) + // Calls Gadgets.xor on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix + return a.map((row, i) => + row.map((value, j) => Gadgets.xor(value, b[i][j], 64)) ); } @@ -255,21 +252,21 @@ function pad101(message: Field[], rate: number): Field[] { // ROUND TRANSFORMATION // First algorithm in the compression step of Keccak for 64-bit words. -// C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4] -// D[x] = C[x-1] xor ROT(C[x+1], 1) -// E[x,y] = A[x,y] xor D[x] +// C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] +// D[i] = C[i-1] xor ROT(C[i+1], 1) +// E[i,j] = A[i,j] xor D[i] // In the Keccak reference, it corresponds to the `theta` algorithm. -// We use the first index of the state array as the x coordinate and the second index as the y coordinate. +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. const theta = (state: Field[][]): Field[][] => { const stateA = state; // XOR the elements of each row together - // for all x in {0..4}: C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4] + // for all i in {0..4}: C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] const stateC = stateA.map((row) => - row.reduce((acc, next) => Gadgets.xor(acc, next, KECCAK_WORD)) + row.reduce((acc, value) => Gadgets.xor(acc, value, KECCAK_WORD)) ); - // for all x in {0..4}: D[x] = C[x-1] xor ROT(C[x+1], 1) + // for all i in {0..4}: D[i] = C[i-1] xor ROT(C[i+1], 1) const stateD = Array.from({ length: KECCAK_DIM }, (_, x) => Gadgets.xor( stateC[(x + KECCAK_DIM - 1) % KECCAK_DIM], @@ -278,7 +275,7 @@ const theta = (state: Field[][]): Field[][] => { ) ); - // for all x in {0..4} and y in {0..4}: E[x,y] = A[x,y] xor D[x] + // for all i in {0..4} and j in {0..4}: E[i,j] = A[i,j] xor D[i] const stateE = stateA.map((row, index) => row.map((elem) => Gadgets.xor(elem, stateD[index], KECCAK_WORD)) ); @@ -287,40 +284,40 @@ const theta = (state: Field[][]): Field[][] => { }; // Second and third steps in the compression step of Keccak for 64-bit words. -// pi: A[x,y] = ROT(E[x,y], r[x,y]) -// rho: A[x,y] = A'[y, 2x+3y mod KECCAK_DIM] -// piRho: B[y,2x+3y] = ROT(E[x,y], r[x,y]) +// pi: A[i,j] = ROT(E[i,j], r[i,j]) +// rho: A[i,j] = A'[j, 2i+3j mod KECCAK_DIM] +// piRho: B[j,2i+3j] = ROT(E[i,j], r[i,j]) // which is equivalent to the `rho` algorithm followed by the `pi` algorithm in the Keccak reference as follows: // rho: // A[0,0] = a[0,0] -// | x | = | 1 | -// | y | = | 0 | +// | i | = | 1 | +// | j | = | 0 | // for t = 0 to 23 do -// A[x,y] = ROT(a[x,y], (t+1)(t+2)/2 mod 64))) -// | x | = | 0 1 | | x | +// A[i,j] = ROT(a[i,j], (t+1)(t+2)/2 mod 64))) +// | i | = | 0 1 | | i | // | | = | | * | | -// | y | = | 2 3 | | y | +// | j | = | 2 3 | | j | // end for // pi: -// for x = 0 to 4 do -// for y = 0 to 4 do -// | X | = | 0 1 | | x | +// for i = 0 to 4 do +// for j = 0 to 4 do +// | I | = | 0 1 | | i | // | | = | | * | | -// | Y | = | 2 3 | | y | -// A[X,Y] = a[x,y] +// | J | = | 2 3 | | j | +// A[I,J] = a[i,j] // end for // end for -// We use the first index of the state array as the x coordinate and the second index as the y coordinate. +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. function piRho(state: Field[][]): Field[][] { const stateE = state; const stateB: Field[][] = getKeccakStateZeros(); - // for all x in {0..4} and y in {0..4}: B[y,2x+3y] = ROT(E[x,y], r[x,y]) - for (let x = 0; x < KECCAK_DIM; x++) { - for (let y = 0; y < KECCAK_DIM; y++) { - stateB[y][(2 * x + 3 * y) % KECCAK_DIM] = Gadgets.rotate( - stateE[x][y], - ROT_TABLE[x][y], + // for all i in {0..4} and j in {0..4}: B[y,2x+3y] = ROT(E[i,j], r[i,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate( + stateE[i][j], + ROT_TABLE[i][j], 'left' ); } @@ -330,26 +327,26 @@ function piRho(state: Field[][]): Field[][] { } // Fourth step of the compression function of Keccak for 64-bit words. -// F[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y]) +// F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) // It corresponds to the chi algorithm in the Keccak reference. -// for y = 0 to 4 do -// for x = 0 to 4 do -// A[x,y] = a[x,y] xor ((not a[x+1,y]) and a[x+2,y]) +// for j = 0 to 4 do +// for i = 0 to 4 do +// A[i,j] = a[i,j] xor ((not a[i+1,j]) and a[i+2,j]) // end for // end for function chi(state: Field[][]): Field[][] { const stateB = state; const stateF = getKeccakStateZeros(); - // for all x in {0..4} and y in {0..4}: F[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y]) - for (let x = 0; x < KECCAK_DIM; x++) { - for (let y = 0; y < KECCAK_DIM; y++) { - stateF[x][y] = Gadgets.xor( - stateB[x][y], + // for all i in {0..4} and j in {0..4}: F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateF[i][j] = Gadgets.xor( + stateB[i][j], Gadgets.and( // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 - Gadgets.not(stateB[(x + 1) % KECCAK_DIM][y], KECCAK_WORD, false), - stateB[(x + 2) % KECCAK_DIM][y], + Gadgets.not(stateB[(i + 1) % KECCAK_DIM][j], KECCAK_WORD, false), + stateB[(i + 2) % KECCAK_DIM][j], KECCAK_WORD ), KECCAK_WORD @@ -383,10 +380,7 @@ function round(state: Field[][], rc: Field): Field[][] { // Keccak permutation function with a constant number of rounds function permutation(state: Field[][], rc: Field[]): Field[][] { - return rc.reduce( - (currentState, rcValue) => round(currentState, rcValue), - state - ); + return rc.reduce((acc, value) => round(acc, value), state); } // KECCAK SPONGE @@ -403,16 +397,16 @@ function absorb( // (capacity / 8) zero bytes const zeros = Array(capacity / 8).fill(Field.from(0)); - for (let i = 0; i < paddedMessage.length; i += rate / 8) { + for (let idx = 0; idx < paddedMessage.length; idx += rate / 8) { // split into blocks of rate bits // for each block of rate bits in the padded message -> this is rate/8 bytes - const block = paddedMessage.slice(i, i + rate / 8); + const block = paddedMessage.slice(idx, idx + rate / 8); // pad the block with 0s to up to 1600 bits const paddedBlock = block.concat(zeros); // padded with zeros each block until they are 1600 bit long assert( paddedBlock.length * 8 === KECCAK_STATE_LENGTH, - 'improper Keccak block length' + `improper Keccak block length (should be ${KECCAK_STATE_LENGTH})` ); const blockState = getKeccakStateOfBytes(paddedBlock); // xor the state with the padded block @@ -438,8 +432,8 @@ function squeeze( start: number, length: number ) => { - for (let i = 0; i < length; i++) { - outputArray[start + i] = bytestring[i]; + for (let idx = 0; idx < length; idx++) { + outputArray[start + idx] = bytestring[idx]; } }; @@ -487,7 +481,7 @@ function sponge( } // load round constants into Fields - let rc = exists(24, () => TupleN.fromArray(24, ROUND_CONSTANTS)); + const rc = exists(24, () => TupleN.fromArray(24, ROUND_CONSTANTS)); // absorb const state = absorb(paddedMessage, capacity, rate, rc); @@ -518,37 +512,34 @@ function hash( capacity: number, nistVersion: boolean ): Field[] { + // Throw errors if used improperly assert(capacity > 0, 'capacity must be positive'); assert( capacity < KECCAK_STATE_LENGTH, - 'capacity must be less than KECCAK_STATE_LENGTH' + `capacity must be less than ${KECCAK_STATE_LENGTH}` ); assert(length > 0, 'length must be positive'); assert(length % 8 === 0, 'length must be a multiple of 8'); // Input endianness conversion - let messageFormatted = inpEndian === 'Big' ? message : message.reverse(); + const messageFormatted = inpEndian === 'Big' ? message : message.reverse(); // Check each Field input is 8 bits at most if it was not done before at creation time - if (byteChecks) { - checkBytes(messageFormatted); - } + byteChecks && checkBytes(messageFormatted); const rate = KECCAK_STATE_LENGTH - capacity; - let padded; - if (nistVersion) { - padded = padNist(messageFormatted, rate); - } else { - padded = pad101(messageFormatted, rate); - } + const padded = + nistVersion === true + ? padNist(messageFormatted, rate) + : pad101(messageFormatted, rate); const hash = sponge(padded, length, capacity, rate); // Always check each Field output is 8 bits at most because they are created here checkBytes(hash); - // Set input to desired endianness + // Output endianness conversion const hashFormatted = outEndian === 'Big' ? hash : hash.reverse(); return hashFormatted; @@ -563,26 +554,18 @@ function nistSha3( outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - let output: Field[]; - switch (len) { case 224: - output = hash(inpEndian, outEndian, byteChecks, message, 224, 448, true); - break; + return hash(inpEndian, outEndian, byteChecks, message, 224, 448, true); case 256: - output = hash(inpEndian, outEndian, byteChecks, message, 256, 512, true); - break; + return hash(inpEndian, outEndian, byteChecks, message, 256, 512, true); case 384: - output = hash(inpEndian, outEndian, byteChecks, message, 384, 768, true); - break; + return hash(inpEndian, outEndian, byteChecks, message, 384, 768, true); case 512: - output = hash(inpEndian, outEndian, byteChecks, message, 512, 1024, true); - break; + return hash(inpEndian, outEndian, byteChecks, message, 512, 1024, true); default: throw new Error('Invalid length'); } - - return output; } // Gadget for Keccak hash function for the parameters used in Ethereum. diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index e72e655093..34e497804e 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -7,12 +7,15 @@ import { Random } from './testing/random.js'; import { array, equivalentAsync, fieldWithRng } from './testing/equivalent.js'; import { constraintSystem, contains } from './testing/constraint-system.js'; +// TODO(jackryanservia): Add test to assert fail for byte that's larger than 255 +// TODO(jackryanservia): Add random length with three runs + const PREIMAGE_LENGTH = 75; const RUNS = 1; -let uint = (length: number) => fieldWithRng(Random.biguint(length)); +const uint = (length: number) => fieldWithRng(Random.biguint(length)); -let Keccak256 = ZkProgram({ +const Keccak256 = ZkProgram({ name: 'keccak256', publicInput: Provable.Array(Field, PREIMAGE_LENGTH), publicOutput: Provable.Array(Field, 32), @@ -43,12 +46,12 @@ await equivalentAsync( { runs: RUNS } )( (x) => { - let uint8Array = new Uint8Array(x.map(Number)); - let result = keccak_256(uint8Array); + const uint8Array = new Uint8Array(x.map(Number)); + const result = keccak_256(uint8Array); return Array.from(result).map(BigInt); }, async (x) => { - let proof = await Keccak256.ethereum(x); + const proof = await Keccak256.ethereum(x); return proof.publicOutput; } ); @@ -61,17 +64,17 @@ await equivalentAsync( { runs: RUNS } )( (x) => { - let thing = x.map(Number); - let result = sha3_256(new Uint8Array(thing)); + const thing = x.map(Number); + const result = sha3_256(new Uint8Array(thing)); return Array.from(result).map(BigInt); }, async (x) => { - let proof = await Keccak256.nistSha3(x); + const proof = await Keccak256.nistSha3(x); return proof.publicOutput; } ); -// let Keccak512 = ZkProgram({ +// const Keccak512 = ZkProgram({ // name: 'keccak512', // publicInput: Provable.Array(Field, PREIMAGE_LENGTH), // publicOutput: Provable.Array(Field, 64), @@ -101,12 +104,12 @@ await equivalentAsync( // { runs: RUNS } // )( // (x) => { -// let uint8Array = new Uint8Array(x.map(Number)); -// let result = keccak_512(uint8Array); +// const uint8Array = new Uint8Array(x.map(Number)); +// const result = keccak_512(uint8Array); // return Array.from(result).map(BigInt); // }, // async (x) => { -// let proof = await Keccak512.preNist(x); +// const proof = await Keccak512.preNist(x); // return proof.publicOutput; // } // ); @@ -119,12 +122,12 @@ await equivalentAsync( // { runs: RUNS } // )( // (x) => { -// let thing = x.map(Number); -// let result = sha3_512(new Uint8Array(thing)); +// const thing = x.map(Number); +// const result = sha3_512(new Uint8Array(thing)); // return Array.from(result).map(BigInt); // }, // async (x) => { -// let proof = await Keccak512.nistSha3(x); +// const proof = await Keccak512.nistSha3(x); // return proof.publicOutput; // } // ); From 9d34c7fcf424ed38246c36ae5b60b9df0d00ded8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 7 Dec 2023 13:37:33 +0100 Subject: [PATCH 1045/1215] make `verify()` backwards-compatible with old vk type --- src/examples/zkprogram/program-with-input.ts | 6 +++--- src/examples/zkprogram/program.ts | 6 +++--- src/lib/proof_system.ts | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/examples/zkprogram/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts index 83265082a2..16aab5ab1c 100644 --- a/src/examples/zkprogram/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -52,7 +52,7 @@ proof = testJsonRoundtrip(MyProof, proof); proof satisfies Proof; console.log('verify...'); -let ok = await verify(proof.toJSON(), verificationKey.data); +let ok = await verify(proof.toJSON(), verificationKey); console.log('ok?', ok); console.log('verify alternative...'); @@ -64,7 +64,7 @@ proof = await MyProgram.inductiveCase(Field(1), proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof, verificationKey.data); +ok = await verify(proof, verificationKey); console.log('ok?', ok); console.log('verify alternative...'); @@ -76,7 +76,7 @@ proof = await MyProgram.inductiveCase(Field(2), proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey.data); +ok = await verify(proof.toJSON(), verificationKey); console.log('ok?', ok && proof.publicInput.toString() === '2'); diff --git a/src/examples/zkprogram/program.ts b/src/examples/zkprogram/program.ts index 0b75880b04..7cc09602b1 100644 --- a/src/examples/zkprogram/program.ts +++ b/src/examples/zkprogram/program.ts @@ -53,7 +53,7 @@ proof = testJsonRoundtrip(MyProof, proof); proof satisfies Proof; console.log('verify...'); -let ok = await verify(proof.toJSON(), verificationKey.data); +let ok = await verify(proof.toJSON(), verificationKey); console.log('ok?', ok); console.log('verify alternative...'); @@ -65,7 +65,7 @@ proof = await MyProgram.inductiveCase(proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof, verificationKey.data); +ok = await verify(proof, verificationKey); console.log('ok?', ok); console.log('verify alternative...'); @@ -77,7 +77,7 @@ proof = await MyProgram.inductiveCase(proof); proof = testJsonRoundtrip(MyProof, proof); console.log('verify...'); -ok = await verify(proof.toJSON(), verificationKey.data); +ok = await verify(proof.toJSON(), verificationKey); console.log('ok?', ok && proof.publicOutput.toString() === '2'); diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 1d3ace221a..59ebf57ec4 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -190,7 +190,7 @@ class Proof { async function verify( proof: Proof | JsonProof, - verificationKey: string + verificationKey: string | VerificationKey ) { let picklesProof: Pickles.Proof; let statement: Pickles.Statement; @@ -215,10 +215,12 @@ async function verify( let output = toFieldConsts(type.output, proof.publicOutput); statement = MlPair(input, output); } + let vk = + typeof verificationKey === 'string' + ? verificationKey + : verificationKey.data; return prettifyStacktracePromise( - withThreadPool(() => - Pickles.verify(statement, picklesProof, verificationKey) - ) + withThreadPool(() => Pickles.verify(statement, picklesProof, vk)) ); } From 884dcffdd8cc4807b709552d5920a2eb9b192b24 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 13:00:37 +0100 Subject: [PATCH 1046/1215] reproduce bug --- src/examples/broken-proof.ts | 103 +++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/examples/broken-proof.ts diff --git a/src/examples/broken-proof.ts b/src/examples/broken-proof.ts new file mode 100644 index 0000000000..4f5310354b --- /dev/null +++ b/src/examples/broken-proof.ts @@ -0,0 +1,103 @@ +import { + AccountUpdate, + Bool, + Mina, + PrivateKey, + SmartContract, + State, + UInt64, + method, + state, + ZkProgram, +} from 'o1js'; +import { tic, toc } from './utils/tic-toc.node.js'; +import assert from 'assert'; + +export const RealProof = ZkProgram({ + name: 'real', + methods: { + make: { + privateInputs: [UInt64], + + method(value: UInt64) { + let expected = UInt64.from(34); + value.assertEquals(expected); + }, + }, + }, +}); + +export const FakeProof = ZkProgram({ + name: 'fake', + methods: { + make: { + privateInputs: [UInt64], + + method(value: UInt64) { + Bool(true).assertTrue(); + }, + }, + }, +}); + +class BrokenProof extends ZkProgram.Proof(RealProof) {} + +class Broken extends SmartContract { + @state(Bool) isValid = State(); + + init() { + super.init(); + this.isValid.set(Bool(false)); + } + + @method setValid(proof: BrokenProof) { + proof.verify(); + + this.isValid.set(Bool(true)); + } +} + +const Local = Mina.LocalBlockchain({ proofsEnabled: true }); +Mina.setActiveInstance(Local); + +let { privateKey: deployerKey, publicKey: deployerAccount } = + Local.testAccounts[0]; +let { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1]; +let zkAppPrivateKey = PrivateKey.random(); +let zkAppAddress = zkAppPrivateKey.toPublicKey(); + +let zkApp = new Broken(zkAppAddress); + +tic('compile'); +await RealProof.compile(); +await FakeProof.compile(); +await Broken.compile(); +toc(); + +// local deploy +tic('deploy'); +const deployTxn = await Mina.transaction(deployerAccount, () => { + AccountUpdate.fundNewAccount(deployerAccount); + zkApp.deploy(); +}); +await deployTxn.prove(); +await deployTxn.sign([deployerKey, zkAppPrivateKey]).send(); +toc(); + +// accepts a fake proof +tic('fake proof'); +const value = UInt64.from(99999); +const fakeProof = await FakeProof.make(value); +toc(); + +tic('set valid'); +const txn = await Mina.transaction(senderAccount, () => { + zkApp.setValid(fakeProof); +}); +let proofs = await txn.prove(); +toc(); +console.log(proofs.find((p) => p !== undefined)); +await txn.sign([senderKey]).send(); + +const isValid = zkApp.isValid.get(); +assert(isValid); From b02a695c8bf36d469350fd2e14537a7c535922ba Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 13:08:08 +0100 Subject: [PATCH 1047/1215] can't reproduce with zkprogram! --- src/examples/broken-proof.ts | 52 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/examples/broken-proof.ts b/src/examples/broken-proof.ts index 4f5310354b..2c454f2633 100644 --- a/src/examples/broken-proof.ts +++ b/src/examples/broken-proof.ts @@ -1,5 +1,4 @@ import { - AccountUpdate, Bool, Mina, PrivateKey, @@ -9,11 +8,12 @@ import { method, state, ZkProgram, + verify, } from 'o1js'; import { tic, toc } from './utils/tic-toc.node.js'; import assert from 'assert'; -export const RealProof = ZkProgram({ +const RealProof = ZkProgram({ name: 'real', methods: { make: { @@ -27,21 +27,32 @@ export const RealProof = ZkProgram({ }, }); -export const FakeProof = ZkProgram({ +const FakeProof = ZkProgram({ name: 'fake', methods: { make: { privateInputs: [UInt64], - method(value: UInt64) { - Bool(true).assertTrue(); - }, + method(value: UInt64) {}, }, }, }); class BrokenProof extends ZkProgram.Proof(RealProof) {} +const BrokenProgram = ZkProgram({ + name: 'broken', + methods: { + verifyReal: { + privateInputs: [BrokenProof], + + method(proof: BrokenProof) { + proof.verify(); + }, + }, + }, +}); + class Broken extends SmartContract { @state(Bool) isValid = State(); @@ -52,7 +63,6 @@ class Broken extends SmartContract { @method setValid(proof: BrokenProof) { proof.verify(); - this.isValid.set(Bool(true)); } } @@ -71,33 +81,15 @@ let zkApp = new Broken(zkAppAddress); tic('compile'); await RealProof.compile(); await FakeProof.compile(); -await Broken.compile(); -toc(); - -// local deploy -tic('deploy'); -const deployTxn = await Mina.transaction(deployerAccount, () => { - AccountUpdate.fundNewAccount(deployerAccount); - zkApp.deploy(); -}); -await deployTxn.prove(); -await deployTxn.sign([deployerKey, zkAppPrivateKey]).send(); +let { verificationKey } = await BrokenProgram.compile(); toc(); -// accepts a fake proof tic('fake proof'); const value = UInt64.from(99999); const fakeProof = await FakeProof.make(value); toc(); -tic('set valid'); -const txn = await Mina.transaction(senderAccount, () => { - zkApp.setValid(fakeProof); -}); -let proofs = await txn.prove(); -toc(); -console.log(proofs.find((p) => p !== undefined)); -await txn.sign([senderKey]).send(); - -const isValid = zkApp.isValid.get(); -assert(isValid); +tic('broken proof'); +const brokenProof = await BrokenProgram.verifyReal(fakeProof); +let ok = await verify(brokenProof, verificationKey.data); +assert(ok); From 4b01adcbf7cb034ec9cdd3c4aad6b1edb6252362 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 13:27:54 +0100 Subject: [PATCH 1048/1215] make reproduction a proper test --- src/examples/broken-proof.ts | 52 ++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/examples/broken-proof.ts b/src/examples/broken-proof.ts index 2c454f2633..317a8e8eba 100644 --- a/src/examples/broken-proof.ts +++ b/src/examples/broken-proof.ts @@ -1,12 +1,9 @@ import { - Bool, Mina, PrivateKey, SmartContract, - State, UInt64, method, - state, ZkProgram, verify, } from 'o1js'; @@ -53,43 +50,40 @@ const BrokenProgram = ZkProgram({ }, }); -class Broken extends SmartContract { - @state(Bool) isValid = State(); - - init() { - super.init(); - this.isValid.set(Bool(false)); - } - - @method setValid(proof: BrokenProof) { +class BrokenContract extends SmartContract { + @method verifyReal(proof: BrokenProof) { proof.verify(); - this.isValid.set(Bool(true)); } } -const Local = Mina.LocalBlockchain({ proofsEnabled: true }); -Mina.setActiveInstance(Local); - -let { privateKey: deployerKey, publicKey: deployerAccount } = - Local.testAccounts[0]; -let { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1]; -let zkAppPrivateKey = PrivateKey.random(); -let zkAppAddress = zkAppPrivateKey.toPublicKey(); - -let zkApp = new Broken(zkAppAddress); +Mina.setActiveInstance(Mina.LocalBlockchain()); +let zkApp = new BrokenContract(PrivateKey.random().toPublicKey()); tic('compile'); await RealProof.compile(); await FakeProof.compile(); -let { verificationKey } = await BrokenProgram.compile(); +let { verificationKey: contractVk } = await BrokenContract.compile(); +let { verificationKey: programVk } = await BrokenProgram.compile(); toc(); -tic('fake proof'); +tic('create fake proof'); const value = UInt64.from(99999); const fakeProof = await FakeProof.make(value); toc(); -tic('broken proof'); -const brokenProof = await BrokenProgram.verifyReal(fakeProof); -let ok = await verify(brokenProof, verificationKey.data); -assert(ok); +await assert.rejects(async () => { + tic('verify fake proof in program'); + const brokenProof = await BrokenProgram.verifyReal(fakeProof); + assert(await verify(brokenProof, programVk.data)); + toc(); +}, 'recursive program rejects fake proof'); + +await assert.rejects(async () => { + tic('verify fake proof in contract'); + let tx = await Mina.transaction(() => zkApp.verifyReal(fakeProof)); + let proof = (await tx.prove()).find((p) => p !== undefined); + assert(proof !== undefined); + console.dir(proof, { depth: 5 }); + assert(await verify(proof, contractVk.data)); + toc(); +}, 'recursive contract rejects fake proof'); From c694915eaaded880efc079783030b5cc6d1cf5f0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 13:57:44 +0100 Subject: [PATCH 1049/1215] fix: don't clone proofs so they can be mutated --- src/lib/circuit_value.ts | 3 ++- src/lib/zkapp.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 85793721b3..1a0f23ab84 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -17,6 +17,7 @@ import type { import { Provable } from './provable.js'; import { assert } from './errors.js'; import { inCheckedComputation } from './provable-context.js'; +import { Proof } from './proof_system.js'; // external API export { @@ -564,7 +565,7 @@ and Provable.asProver() blocks, which execute outside the proof. }; } -let primitives = new Set([Field, Bool, Scalar, Group]); +let primitives = new Set([Field, Bool, Scalar, Group, Proof]); function isPrimitive(obj: any) { for (let P of primitives) { if (obj instanceof P) return true; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 45dd3a0424..6986966a3d 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -22,7 +22,6 @@ import { FlexibleProvablePure, InferProvable, provable, - Struct, toConstant, } from './circuit_value.js'; import { Provable, getBlindingValue, memoizationContext } from './provable.js'; @@ -196,7 +195,8 @@ function wrapMethod( let id = memoizationContext.enter({ ...context, blindingValue }); let result: unknown; try { - result = method.apply(this, actualArgs.map(cloneCircuitValue)); + let clonedArgs = actualArgs.map(cloneCircuitValue); + result = method.apply(this, clonedArgs); } finally { memoizationContext.leave(id); } From fa713077e0d6bd203ce8d70fd432af173c2b1307 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 13:58:09 +0100 Subject: [PATCH 1050/1215] tweak test some more --- src/examples/broken-proof.ts | 53 +++++++++++++++++------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/examples/broken-proof.ts b/src/examples/broken-proof.ts index 317a8e8eba..60c5a66623 100644 --- a/src/examples/broken-proof.ts +++ b/src/examples/broken-proof.ts @@ -10,12 +10,11 @@ import { import { tic, toc } from './utils/tic-toc.node.js'; import assert from 'assert'; -const RealProof = ZkProgram({ +const RealProgram = ZkProgram({ name: 'real', methods: { make: { privateInputs: [UInt64], - method(value: UInt64) { let expected = UInt64.from(34); value.assertEquals(expected); @@ -24,66 +23,64 @@ const RealProof = ZkProgram({ }, }); -const FakeProof = ZkProgram({ +const FakeProgram = ZkProgram({ name: 'fake', methods: { - make: { - privateInputs: [UInt64], - - method(value: UInt64) {}, - }, + make: { privateInputs: [UInt64], method(_: UInt64) {} }, }, }); -class BrokenProof extends ZkProgram.Proof(RealProof) {} +class RealProof extends ZkProgram.Proof(RealProgram) {} -const BrokenProgram = ZkProgram({ +const RecursiveProgram = ZkProgram({ name: 'broken', methods: { verifyReal: { - privateInputs: [BrokenProof], - - method(proof: BrokenProof) { + privateInputs: [RealProof], + method(proof: RealProof) { proof.verify(); }, }, }, }); -class BrokenContract extends SmartContract { - @method verifyReal(proof: BrokenProof) { +class RecursiveContract extends SmartContract { + @method verifyReal(proof: RealProof) { proof.verify(); } } Mina.setActiveInstance(Mina.LocalBlockchain()); -let zkApp = new BrokenContract(PrivateKey.random().toPublicKey()); tic('compile'); -await RealProof.compile(); -await FakeProof.compile(); -let { verificationKey: contractVk } = await BrokenContract.compile(); -let { verificationKey: programVk } = await BrokenProgram.compile(); +await RealProgram.compile(); +await FakeProgram.compile(); +let { verificationKey: contractVk } = await RecursiveContract.compile(); +let { verificationKey: programVk } = await RecursiveProgram.compile(); toc(); tic('create fake proof'); const value = UInt64.from(99999); -const fakeProof = await FakeProof.make(value); +const fakeProof = await FakeProgram.make(value); toc(); +tic('verify fake proof in program'); await assert.rejects(async () => { - tic('verify fake proof in program'); - const brokenProof = await BrokenProgram.verifyReal(fakeProof); + const brokenProof = await RecursiveProgram.verifyReal(fakeProof); assert(await verify(brokenProof, programVk.data)); - toc(); }, 'recursive program rejects fake proof'); +toc(); +let publicKey = PrivateKey.random().toPublicKey(); +let zkApp = new RecursiveContract(publicKey); + +tic('verify fake proof in contract'); await assert.rejects(async () => { - tic('verify fake proof in contract'); - let tx = await Mina.transaction(() => zkApp.verifyReal(fakeProof)); + let tx = await Mina.transaction(() => { + zkApp.verifyReal(fakeProof); + }); let proof = (await tx.prove()).find((p) => p !== undefined); assert(proof !== undefined); - console.dir(proof, { depth: 5 }); assert(await verify(proof, contractVk.data)); - toc(); }, 'recursive contract rejects fake proof'); +toc(); From 5efd81463a39b30563ff7ced8b80fc69fdd720ec Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:11:55 +0100 Subject: [PATCH 1051/1215] move example to test folder and add more test cases --- .../broken-proof.ts => tests/fake-proof.ts} | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) rename src/{examples/broken-proof.ts => tests/fake-proof.ts} (50%) diff --git a/src/examples/broken-proof.ts b/src/tests/fake-proof.ts similarity index 50% rename from src/examples/broken-proof.ts rename to src/tests/fake-proof.ts index 60c5a66623..e06c1bd61d 100644 --- a/src/examples/broken-proof.ts +++ b/src/tests/fake-proof.ts @@ -7,7 +7,6 @@ import { ZkProgram, verify, } from 'o1js'; -import { tic, toc } from './utils/tic-toc.node.js'; import assert from 'assert'; const RealProgram = ZkProgram({ @@ -51,36 +50,48 @@ class RecursiveContract extends SmartContract { } Mina.setActiveInstance(Mina.LocalBlockchain()); +let publicKey = PrivateKey.random().toPublicKey(); +let zkApp = new RecursiveContract(publicKey); -tic('compile'); await RealProgram.compile(); await FakeProgram.compile(); let { verificationKey: contractVk } = await RecursiveContract.compile(); let { verificationKey: programVk } = await RecursiveProgram.compile(); -toc(); -tic('create fake proof'); -const value = UInt64.from(99999); -const fakeProof = await FakeProgram.make(value); -toc(); +// proof that should be rejected +const fakeProof = await FakeProgram.make(UInt64.from(99999)); +const dummyProof = await RealProof.dummy(undefined, undefined, 0); -tic('verify fake proof in program'); -await assert.rejects(async () => { - const brokenProof = await RecursiveProgram.verifyReal(fakeProof); - assert(await verify(brokenProof, programVk.data)); -}, 'recursive program rejects fake proof'); -toc(); +for (let proof of [fakeProof, dummyProof]) { + // zkprogram rejects proof + await assert.rejects(async () => { + const brokenProof = await RecursiveProgram.verifyReal(proof); + assert(await verify(brokenProof, programVk.data)); + }, 'recursive program rejects fake proof'); -let publicKey = PrivateKey.random().toPublicKey(); -let zkApp = new RecursiveContract(publicKey); + // contract rejects proof + await assert.rejects(async () => { + let tx = await Mina.transaction(() => zkApp.verifyReal(proof)); + await tx.prove(); + }, 'recursive contract rejects fake proof'); +} + +// proof that should be accepted +const realProof = await RealProgram.make(UInt64.from(34)); + +// zkprogram accepts proof +const brokenProof = await RecursiveProgram.verifyReal(realProof); +assert( + await verify(brokenProof, programVk.data), + 'recursive program accepts real proof' +); + +// contract rejects proof +let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); +let [contractProof] = await tx.prove(); +assert( + await verify(contractProof!, contractVk.data), + 'recursive contract accepts real proof' +); -tic('verify fake proof in contract'); -await assert.rejects(async () => { - let tx = await Mina.transaction(() => { - zkApp.verifyReal(fakeProof); - }); - let proof = (await tx.prove()).find((p) => p !== undefined); - assert(proof !== undefined); - assert(await verify(proof, contractVk.data)); -}, 'recursive contract rejects fake proof'); -toc(); +console.log('fake proof test passed 🎉'); From a43bd3cbbae04c8be6a2cddf1a37a5be5c986803 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:14:13 +0100 Subject: [PATCH 1052/1215] add to integration tests --- run-ci-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run-ci-tests.sh b/run-ci-tests.sh index cfcdf2e4c0..1e2406df15 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -8,6 +8,7 @@ case $TEST_TYPE in ./run src/examples/simple_zkapp.ts --bundle ./run src/examples/zkapps/reducer/reducer_composite.ts --bundle ./run src/examples/zkapps/composability.ts --bundle + ./run src/tests/fake-proofs.ts --bundle ;; "Voting integration tests") From e2cf69951119e66f36066d71bcc923c1524b6538 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:14:35 +0100 Subject: [PATCH 1053/1215] minor correction --- run-ci-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-ci-tests.sh b/run-ci-tests.sh index 1e2406df15..fc815491bc 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -8,7 +8,7 @@ case $TEST_TYPE in ./run src/examples/simple_zkapp.ts --bundle ./run src/examples/zkapps/reducer/reducer_composite.ts --bundle ./run src/examples/zkapps/composability.ts --bundle - ./run src/tests/fake-proofs.ts --bundle + ./run src/tests/fake-proofs.ts ;; "Voting integration tests") From 44c0024e1f7ab83ebc59c37b37c59f82c14b48bd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:25:42 +0100 Subject: [PATCH 1054/1215] fixup --- run-ci-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-ci-tests.sh b/run-ci-tests.sh index fc815491bc..4b19ebd25d 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -8,7 +8,7 @@ case $TEST_TYPE in ./run src/examples/simple_zkapp.ts --bundle ./run src/examples/zkapps/reducer/reducer_composite.ts --bundle ./run src/examples/zkapps/composability.ts --bundle - ./run src/tests/fake-proofs.ts + ./run src/tests/fake-proof.ts ;; "Voting integration tests") From 23edcf0c9f9ce439d74bb26189e66ee6865e29ad Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:29:30 +0100 Subject: [PATCH 1055/1215] change fix to avoid import cycle --- src/lib/circuit_value.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 1a0f23ab84..6c4c540106 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -565,7 +565,7 @@ and Provable.asProver() blocks, which execute outside the proof. }; } -let primitives = new Set([Field, Bool, Scalar, Group, Proof]); +let primitives = new Set([Field, Bool, Scalar, Group]); function isPrimitive(obj: any) { for (let P of primitives) { if (obj instanceof P) return true; @@ -598,10 +598,13 @@ function cloneCircuitValue(obj: T): T { ) as any as T; if (ArrayBuffer.isView(obj)) return new (obj.constructor as any)(obj); - // o1js primitives aren't cloned + // o1js primitives and proofs aren't cloned if (isPrimitive(obj)) { return obj; } + if (obj instanceof Proof) { + return obj; + } // cloning strategy that works for plain objects AND classes whose constructor only assigns properties let propertyDescriptors: Record = {}; From 4b96d792fc1c45f5ab0970279f27c38ed1225c28 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 14:50:06 +0100 Subject: [PATCH 1056/1215] address feedback and remove unreachable test code --- src/tests/fake-proof.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts index e06c1bd61d..3607494be1 100644 --- a/src/tests/fake-proof.ts +++ b/src/tests/fake-proof.ts @@ -65,8 +65,7 @@ const dummyProof = await RealProof.dummy(undefined, undefined, 0); for (let proof of [fakeProof, dummyProof]) { // zkprogram rejects proof await assert.rejects(async () => { - const brokenProof = await RecursiveProgram.verifyReal(proof); - assert(await verify(brokenProof, programVk.data)); + await RecursiveProgram.verifyReal(proof); }, 'recursive program rejects fake proof'); // contract rejects proof @@ -86,7 +85,7 @@ assert( 'recursive program accepts real proof' ); -// contract rejects proof +// contract accepts proof let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); let [contractProof] = await tx.prove(); assert( From 37d871993172068b1469f3cf9ef2af9f18dbc953 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 15:40:28 +0100 Subject: [PATCH 1057/1215] reuse some helper types --- src/lib/testing/equivalent.ts | 7 +------ src/lib/util/types.ts | 4 +++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 183281bbba..2eb9ba305f 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -5,6 +5,7 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; import { Bool, Field } from '../core.js'; +import { AnyFunction, Tuple } from '../util/types.js'; export { equivalent, @@ -433,12 +434,6 @@ function throwError(message?: string): any { throw Error(message); } -// helper types - -type AnyFunction = (...args: any) => any; - -type Tuple = [] | [T, ...T[]]; - // infer input types from specs type Param1> = In extends { diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index 7096aa4e73..3256e60476 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -1,6 +1,8 @@ import { assert } from '../errors.js'; -export { Tuple, TupleN, AnyTuple, TupleMap }; +export { AnyFunction, Tuple, TupleN, AnyTuple, TupleMap }; + +type AnyFunction = (...args: any) => any; type Tuple = [T, ...T[]] | []; type AnyTuple = Tuple; From 13e3c978b362bb855266d6ea568294f9ac1b1f8b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 16:19:11 +0100 Subject: [PATCH 1058/1215] add unit test --- src/lib/proof_system.unit-test.ts | 102 +++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index 54b95e6d45..e1368c0c05 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -1,20 +1,106 @@ -import { Field } from './core.js'; +import { Field, Bool } from './core.js'; import { Struct } from './circuit_value.js'; import { UInt64 } from './int.js'; -import { ZkProgram } from './proof_system.js'; +import { + CompiledTag, + Empty, + Proof, + ZkProgram, + picklesRuleFromFunction, + sortMethodArguments, +} from './proof_system.js'; import { expect } from 'expect'; +import { Pickles, ProvablePure, Snarky } from '../snarky.js'; +import { AnyFunction } from './util/types.js'; +import { snarkContext } from './provable-context.js'; +import { it } from 'node:test'; +import { Provable } from './provable.js'; +import { bool, equivalentAsync, field, record } from './testing/equivalent.js'; +import { FieldConst, FieldVar } from './field.js'; const EmptyProgram = ZkProgram({ name: 'empty', publicInput: Field, - methods: { - run: { - privateInputs: [], - method: (publicInput: Field) => {}, - }, - }, + methods: { run: { privateInputs: [], method: (_) => {} } }, +}); + +class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} + +// unit-test zkprogram creation helpers: +// -) sortMethodArguments +// -) picklesRuleFromFunction + +it('pickles rule creation', async () => { + // a rule that verifies a proof conditionally, and returns the proof's input as output + function main(proof: EmptyProof, shouldVerify: Bool) { + proof.verifyIf(shouldVerify); + return proof.publicInput; + } + let privateInputs = [EmptyProof, Bool]; + + // collect method interface + let methodIntf = sortMethodArguments('mock', 'main', privateInputs, Proof); + + expect(methodIntf).toEqual({ + methodName: 'main', + witnessArgs: [Bool], + proofArgs: [EmptyProof], + allArgs: [ + { type: 'proof', index: 0 }, + { type: 'witness', index: 0 }, + ], + genericArgs: [], + }); + + // store compiled tag + CompiledTag.store(EmptyProgram, 'mock tag'); + + // create pickles rule + let rule: Pickles.Rule = picklesRuleFromFunction( + Empty as ProvablePure, + Field as ProvablePure, + main as AnyFunction, + { name: 'mock' }, + methodIntf, + [] + ); + + await equivalentAsync( + { from: [field, bool], to: record({ field, bool }) }, + { runs: 5 } + )( + (field, bool) => ({ field, bool }), + async (field, bool) => { + let dummy = await EmptyProof.dummy(field, undefined, 0); + let field_: FieldConst = [0, 0n]; + let bool_: FieldConst = [0, 0n]; + + Provable.runAndCheck(() => { + // put witnesses in snark context + snarkContext.get().witnesses = [dummy, bool]; + + // call pickles rule + let { + publicOutput: [, publicOutput], + shouldVerify: [, shouldVerify], + } = rule.main([0]); + + // `publicOutput` and `shouldVerify` are as expected + Snarky.field.assertEqual(publicOutput, dummy.publicInput.value); + Snarky.field.assertEqual(shouldVerify, bool.value); + + Provable.asProver(() => { + field_ = Snarky.field.readVar(publicOutput); + bool_ = Snarky.field.readVar(shouldVerify); + }); + }); + + return { field: Field(field_), bool: Bool(FieldVar.constant(bool_)) }; + } + ); }); +// regression tests for some zkprograms const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); expect(emptyMethodsMetadata.run).toEqual( expect.objectContaining({ From 515b1c0b4670e4c3605a5e1b0d90520f591f8b98 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 11 Dec 2023 16:25:07 +0100 Subject: [PATCH 1059/1215] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4e80dcab..7488208b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 - `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `print()` method for pretty-printing the constraint system https://github.com/o1-labs/o1js/pull/1240 +### Fixed + +- Fix missing recursive verification of proofs in smart contracts https://github.com/o1-labs/o1js/pull/1302 + ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) ### Breaking changes From 6e477e260e9dc27ec3be4ee534695cfc76204a87 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:48:44 +0000 Subject: [PATCH 1060/1215] Fix round constants not constrained --- src/lib/keccak.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 813b53d576..48e71658d1 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -2,7 +2,6 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; import { existsOne, exists } from './gadgets/common.js'; -import { TupleN } from './util/types.js'; import { rangeCheck8 } from './gadgets/range-check.js'; export { Keccak }; @@ -359,17 +358,17 @@ function chi(state: Field[][]): Field[][] { // Fifth step of the permutation function of Keccak for 64-bit words. // It takes the word located at the position (0,0) of the state and XORs it with the round constant. -function iota(state: Field[][], rc: Field): Field[][] { +function iota(state: Field[][], rc: bigint): Field[][] { const stateG = state; - stateG[0][0] = Gadgets.xor(stateG[0][0], rc, KECCAK_WORD); + stateG[0][0] = Gadgets.xor(stateG[0][0], Field.from(rc), KECCAK_WORD); return stateG; } // One round of the Keccak permutation function. // iota o chi o pi o rho o theta -function round(state: Field[][], rc: Field): Field[][] { +function round(state: Field[][], rc: bigint): Field[][] { const stateA = state; const stateE = theta(stateA); const stateB = piRho(stateE); @@ -379,7 +378,7 @@ function round(state: Field[][], rc: Field): Field[][] { } // Keccak permutation function with a constant number of rounds -function permutation(state: Field[][], rc: Field[]): Field[][] { +function permutation(state: Field[][], rc: bigint[]): Field[][] { return rc.reduce((acc, value) => round(acc, value), state); } @@ -390,7 +389,7 @@ function absorb( paddedMessage: Field[], capacity: number, rate: number, - rc: Field[] + rc: bigint[] ): Field[][] { let state = getKeccakStateZeros(); @@ -423,7 +422,7 @@ function squeeze( state: Field[][], length: number, rate: number, - rc: Field[] + rc: bigint[] ): Field[] { // Copies a section of bytes in the bytestring into the output array const copy = ( @@ -480,8 +479,8 @@ function sponge( throw new Error('Invalid padded message length'); } - // load round constants into Fields - const rc = exists(24, () => TupleN.fromArray(24, ROUND_CONSTANTS)); + // load round constants + const rc = ROUND_CONSTANTS; // absorb const state = absorb(paddedMessage, capacity, rate, rc); From cc31b398bba5d9da83bb7f4fb07f68c19270b1e7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 11 Dec 2023 17:41:20 -0800 Subject: [PATCH 1061/1215] feat(release.yml): add condition to run jobs only if RUN_JOB environment variable is set to 'true' to prevent unnecessary job runs --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b61097873..a3aa4226dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,15 +51,18 @@ jobs: echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV - name: Install npm dependencies + if: ${{ env.RUN_JOB }} == 'true' run: npm install - name: Update CHANGELOG.md + if: ${{ env.RUN_JOB }} == 'true' run: | npm run update-changelog git add CHANGELOG.md git commit -m "Update CHANGELOG for new version $NEW_VERSION" - name: Create new release branch + if: ${{ env.RUN_JOB }} == 'true' run: | NEW_BRANCH="release/${NEW_VERSION}" git checkout -b $NEW_BRANCH From 601e07aed2e1f23dbfdb4cbf3bfd53e3f259e83a Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 02:21:51 +0000 Subject: [PATCH 1062/1215] Remove switch statements for length --- src/lib/keccak.ts | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 48e71658d1..d7890353dd 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -9,7 +9,7 @@ export { Keccak }; const Keccak = { /** TODO */ nistSha3( - len: number, + len: 224 | 256 | 384 | 512, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', @@ -28,7 +28,7 @@ const Keccak = { }, /** TODO */ preNist( - len: number, + len: 224 | 256 | 384 | 512, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', @@ -547,24 +547,13 @@ function hash( // Gadget for NIST SHA-3 function for output lengths 224/256/384/512. // Input and output endianness can be specified. Default is big endian. function nistSha3( - len: number, + len: 224 | 256 | 384 | 512, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - switch (len) { - case 224: - return hash(inpEndian, outEndian, byteChecks, message, 224, 448, true); - case 256: - return hash(inpEndian, outEndian, byteChecks, message, 256, 512, true); - case 384: - return hash(inpEndian, outEndian, byteChecks, message, 384, 768, true); - case 512: - return hash(inpEndian, outEndian, byteChecks, message, 512, 1024, true); - default: - throw new Error('Invalid length'); - } + return hash(inpEndian, outEndian, byteChecks, message, len, 2 * len, true); } // Gadget for Keccak hash function for the parameters used in Ethereum. @@ -582,22 +571,11 @@ function ethereum( // Input and output endianness can be specified. Default is big endian. // Note that when calling with output length 256 this is equivalent to the ethereum function function preNist( - len: number, + len: 224 | 256 | 384 | 512, message: Field[], inpEndian: 'Big' | 'Little' = 'Big', outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - switch (len) { - case 224: - return hash(inpEndian, outEndian, byteChecks, message, 224, 448, false); - case 256: - return ethereum(message, inpEndian, outEndian, byteChecks); - case 384: - return hash(inpEndian, outEndian, byteChecks, message, 384, 768, false); - case 512: - return hash(inpEndian, outEndian, byteChecks, message, 512, 1024, false); - default: - throw new Error('Invalid length'); - } + return hash(inpEndian, outEndian, byteChecks, message, len, 2 * len, false); } From 8fc218b96f6b8bf858d6d0b234667a58ea7752ce Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 02:42:46 +0000 Subject: [PATCH 1063/1215] Remove endian conversion --- src/lib/keccak.ts | 55 +++++++++++------------------------------------ 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index d7890353dd..585348c51a 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,7 +1,7 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; -import { existsOne, exists } from './gadgets/common.js'; +import { existsOne } from './gadgets/common.js'; import { rangeCheck8 } from './gadgets/range-check.js'; export { Keccak }; @@ -11,30 +11,21 @@ const Keccak = { nistSha3( len: 224 | 256 | 384 | 512, message: Field[], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - return nistSha3(len, message, inpEndian, outEndian, byteChecks); + return nistSha3(len, message, byteChecks); }, /** TODO */ - ethereum( - message: Field[], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', - byteChecks: boolean = false - ): Field[] { - return ethereum(message, inpEndian, outEndian, byteChecks); + ethereum(message: Field[], byteChecks: boolean = false): Field[] { + return ethereum(message, byteChecks); }, /** TODO */ preNist( len: 224 | 256 | 384 | 512, message: Field[], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - return preNist(len, message, inpEndian, outEndian, byteChecks); + return preNist(len, message, byteChecks); }, }; @@ -503,8 +494,6 @@ function checkBytes(inputs: Field[]): void { // - the 10*1 pad will take place after the message, until reaching the bit length rate. // - then, {0} pad will take place to finish the 1600 bits of the state. function hash( - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false, message: Field[] = [], length: number, @@ -520,62 +509,42 @@ function hash( assert(length > 0, 'length must be positive'); assert(length % 8 === 0, 'length must be a multiple of 8'); - // Input endianness conversion - const messageFormatted = inpEndian === 'Big' ? message : message.reverse(); - // Check each Field input is 8 bits at most if it was not done before at creation time - byteChecks && checkBytes(messageFormatted); + byteChecks && checkBytes(message); const rate = KECCAK_STATE_LENGTH - capacity; const padded = - nistVersion === true - ? padNist(messageFormatted, rate) - : pad101(messageFormatted, rate); + nistVersion === true ? padNist(message, rate) : pad101(message, rate); const hash = sponge(padded, length, capacity, rate); // Always check each Field output is 8 bits at most because they are created here checkBytes(hash); - // Output endianness conversion - const hashFormatted = outEndian === 'Big' ? hash : hash.reverse(); - - return hashFormatted; + return hash; } // Gadget for NIST SHA-3 function for output lengths 224/256/384/512. -// Input and output endianness can be specified. Default is big endian. function nistSha3( len: 224 | 256 | 384 | 512, message: Field[], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - return hash(inpEndian, outEndian, byteChecks, message, len, 2 * len, true); + return hash(byteChecks, message, len, 2 * len, true); } // Gadget for Keccak hash function for the parameters used in Ethereum. -// Input and output endianness can be specified. Default is big endian. -function ethereum( - message: Field[] = [], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', - byteChecks: boolean = false -): Field[] { - return hash(inpEndian, outEndian, byteChecks, message, 256, 512, false); +function ethereum(message: Field[] = [], byteChecks: boolean = false): Field[] { + return hash(byteChecks, message, 256, 512, false); } // Gadget for pre-NIST SHA-3 function for output lengths 224/256/384/512. -// Input and output endianness can be specified. Default is big endian. // Note that when calling with output length 256 this is equivalent to the ethereum function function preNist( len: 224 | 256 | 384 | 512, message: Field[], - inpEndian: 'Big' | 'Little' = 'Big', - outEndian: 'Big' | 'Little' = 'Big', byteChecks: boolean = false ): Field[] { - return hash(inpEndian, outEndian, byteChecks, message, len, 2 * len, false); + return hash(byteChecks, message, len, 2 * len, false); } From 86ccad775045801c02e9f7a4bae690c2bb34fdcf Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 02:50:17 +0000 Subject: [PATCH 1064/1215] Replace copy in squeeze with splice builtin --- src/lib/keccak.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 585348c51a..d64963b25c 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -415,18 +415,6 @@ function squeeze( rate: number, rc: bigint[] ): Field[] { - // Copies a section of bytes in the bytestring into the output array - const copy = ( - bytestring: Field[], - outputArray: Field[], - start: number, - length: number - ) => { - for (let idx = 0; idx < length; idx++) { - outputArray[start + idx] = bytestring[idx]; - } - }; - let newState = state; // bytes per squeeze @@ -440,7 +428,8 @@ function squeeze( // first state to be squeezed const bytestring = keccakStateToBytes(state); const outputBytes = bytestring.slice(0, bytesPerSqueeze); - copy(outputBytes, outputArray, 0, bytesPerSqueeze); + // copies a section of bytes in the bytestring into the output array + outputArray.splice(0, bytesPerSqueeze, ...outputBytes); // for the rest of squeezes for (let i = 1; i < squeezes; i++) { @@ -449,7 +438,8 @@ function squeeze( // append the output of the permutation function to the output const bytestringI = keccakStateToBytes(state); const outputBytesI = bytestringI.slice(0, bytesPerSqueeze); - copy(outputBytesI, outputArray, bytesPerSqueeze * i, bytesPerSqueeze); + // copies a section of bytes in the bytestring into the output array + outputArray.splice(bytesPerSqueeze * i, bytesPerSqueeze, ...outputBytesI); } // Obtain the hash selecting the first bitlength/8 bytes of the output array From 3d3977fb28c313fa6d4d8c1578f2c620be03366b Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 04:29:26 +0000 Subject: [PATCH 1065/1215] Combines pad functions and change rate argument to bytes --- src/lib/keccak.ts | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index d64963b25c..bac20da5d8 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -190,49 +190,26 @@ function keccakStateXor(a: Field[][], b: Field[][]): Field[][] { // Computes the number of required extra bytes to pad a message of length bytes function bytesToPad(rate: number, length: number): number { - return Math.floor(rate / 8) - (length % Math.floor(rate / 8)); + return rate - (length % rate); } // Pads a message M as: // M || pad[x](|M|) -// Padding rule 0x06 ..0*..1. -// The padded message vector will start with the message vector -// followed by the 0*1 rule to fulfill a length that is a multiple of rate (in bytes) -// (This means a 0110 sequence, followed with as many 0s as needed, and a final 1 bit) -function padNist(message: Field[], rate: number): Field[] { +// The padded message will begin with the message and end with the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). +// If nist is true, then the padding rule is 0x06 ..0*..1. +// If nist is false, then the padding rule is 10*1. +function pad(message: Field[], rate: number, nist: boolean): Field[] { // Find out desired length of the padding in bytes // If message is already rate bits, need to pad full rate again const extraBytes = bytesToPad(rate, message.length); // 0x06 0x00 ... 0x00 0x80 or 0x86 - const last = Field.from(BigInt(2) ** BigInt(7)); + const first = nist ? 0x06n : 0x01n; + const last = 0x80n; // Create the padding vector const pad = Array(extraBytes).fill(Field.from(0)); - pad[0] = Field.from(6); - pad[extraBytes - 1] = pad[extraBytes - 1].add(last); - - // Return the padded message - return [...message, ...pad]; -} - -// Pads a message M as: -// M || pad[x](|M|) -// Padding rule 10*1. -// The padded message vector will start with the message vector -// followed by the 10*1 rule to fulfill a length that is a multiple of rate (in bytes) -// (This means a 1 bit, followed with as many 0s as needed, and a final 1 bit) -function pad101(message: Field[], rate: number): Field[] { - // Find out desired length of the padding in bytes - // If message is already rate bits, need to pad full rate again - const extraBytes = bytesToPad(rate, message.length); - - // 0x01 0x00 ... 0x00 0x80 or 0x81 - const last = Field.from(BigInt(2) ** BigInt(7)); - - // Create the padding vector - const pad = Array(extraBytes).fill(Field.from(0)); - pad[0] = Field.from(1); + pad[0] = Field.from(first); pad[extraBytes - 1] = pad[extraBytes - 1].add(last); // Return the padded message @@ -504,8 +481,7 @@ function hash( const rate = KECCAK_STATE_LENGTH - capacity; - const padded = - nistVersion === true ? padNist(message, rate) : pad101(message, rate); + const padded = pad(message, rate, nistVersion); const hash = sponge(padded, length, capacity, rate); From 45fb784290f2cdce18f36e69fc431aec71924df7 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 07:06:41 +0000 Subject: [PATCH 1066/1215] Changes all rate, length, and capacity units to bytes --- src/lib/keccak.ts | 68 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index bac20da5d8..55782214f3 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -46,6 +46,9 @@ const BYTES_PER_WORD = KECCAK_WORD / 8; // Length of the state in bits, meaning the 5x5 matrix of words in bits (1600) const KECCAK_STATE_LENGTH = KECCAK_DIM ** 2 * KECCAK_WORD; +// Length of the state in bytes, meaning the 5x5 matrix of words in bytes (200) +const KECCAK_STATE_LENGTH_BYTES = KECCAK_STATE_LENGTH / 8; + // Number of rounds of the Keccak permutation function depending on the value `l` (24) const KECCAK_ROUNDS = 12 + 2 * KECCAK_ELL; @@ -141,9 +144,8 @@ function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { // Converts a state of Fields to a list of bytes as Fields and creates constraints for it function keccakStateToBytes(state: Field[][]): Field[] { - const stateLengthInBytes = KECCAK_STATE_LENGTH / 8; const bytestring: Field[] = Array.from( - { length: stateLengthInBytes }, + { length: KECCAK_STATE_LENGTH_BYTES }, (_, idx) => existsOne(() => { // idx = k + 8 * ((dim * j) + i) @@ -361,19 +363,19 @@ function absorb( ): Field[][] { let state = getKeccakStateZeros(); - // (capacity / 8) zero bytes - const zeros = Array(capacity / 8).fill(Field.from(0)); + // array of capacity zero bytes + const zeros = Array(capacity).fill(Field.from(0)); - for (let idx = 0; idx < paddedMessage.length; idx += rate / 8) { + for (let idx = 0; idx < paddedMessage.length; idx += rate) { // split into blocks of rate bits - // for each block of rate bits in the padded message -> this is rate/8 bytes - const block = paddedMessage.slice(idx, idx + rate / 8); - // pad the block with 0s to up to 1600 bits + // for each block of rate bits in the padded message -> this is rate bytes + const block = paddedMessage.slice(idx, idx + rate); + // pad the block with 0s to up to 200 bytes const paddedBlock = block.concat(zeros); - // padded with zeros each block until they are 1600 bit long + // padded with zeros each block until they are 200 bytes long assert( - paddedBlock.length * 8 === KECCAK_STATE_LENGTH, - `improper Keccak block length (should be ${KECCAK_STATE_LENGTH})` + paddedBlock.length === KECCAK_STATE_LENGTH_BYTES, + `improper Keccak block length (should be ${KECCAK_STATE_LENGTH_BYTES})` ); const blockState = getKeccakStateOfBytes(paddedBlock); // xor the state with the padded block @@ -394,19 +396,17 @@ function squeeze( ): Field[] { let newState = state; - // bytes per squeeze - const bytesPerSqueeze = rate / 8; // number of squeezes const squeezes = Math.floor(length / rate) + 1; // multiple of rate that is larger than output_length, in bytes - const outputLength = squeezes * bytesPerSqueeze; + const outputLength = squeezes * rate; // array with sufficient space to store the output const outputArray = Array(outputLength).fill(Field.from(0)); // first state to be squeezed const bytestring = keccakStateToBytes(state); - const outputBytes = bytestring.slice(0, bytesPerSqueeze); + const outputBytes = bytestring.slice(0, rate); // copies a section of bytes in the bytestring into the output array - outputArray.splice(0, bytesPerSqueeze, ...outputBytes); + outputArray.splice(0, rate, ...outputBytes); // for the rest of squeezes for (let i = 1; i < squeezes; i++) { @@ -414,18 +414,17 @@ function squeeze( newState = permutation(newState, rc); // append the output of the permutation function to the output const bytestringI = keccakStateToBytes(state); - const outputBytesI = bytestringI.slice(0, bytesPerSqueeze); + const outputBytesI = bytestringI.slice(0, rate); // copies a section of bytes in the bytestring into the output array - outputArray.splice(bytesPerSqueeze * i, bytesPerSqueeze, ...outputBytesI); + outputArray.splice(rate * i, rate, ...outputBytesI); } - // Obtain the hash selecting the first bitlength/8 bytes of the output array - const hashed = outputArray.slice(0, length / 8); + // Obtain the hash selecting the first bitlength bytes of the output array + const hashed = outputArray.slice(0, length); return hashed; } -// Keccak sponge function for 1600 bits of state width -// Need to split the message into blocks of 1088 bits. +// Keccak sponge function for 200 bytes of state width function sponge( paddedMessage: Field[], length: number, @@ -433,7 +432,7 @@ function sponge( rate: number ): Field[] { // check that the padded message is a multiple of rate - if ((paddedMessage.length * 8) % rate !== 0) { + if (paddedMessage.length % rate !== 0) { throw new Error('Invalid padded message length'); } @@ -459,7 +458,7 @@ function checkBytes(inputs: Field[]): void { // The message will be parsed as follows: // - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) // - the 10*1 pad will take place after the message, until reaching the bit length rate. -// - then, {0} pad will take place to finish the 1600 bits of the state. +// - then, {0} pad will take place to finish the 200 bytes of the state. function hash( byteChecks: boolean = false, message: Field[] = [], @@ -470,16 +469,15 @@ function hash( // Throw errors if used improperly assert(capacity > 0, 'capacity must be positive'); assert( - capacity < KECCAK_STATE_LENGTH, - `capacity must be less than ${KECCAK_STATE_LENGTH}` + capacity < KECCAK_STATE_LENGTH_BYTES, + `capacity must be less than ${KECCAK_STATE_LENGTH_BYTES}` ); assert(length > 0, 'length must be positive'); - assert(length % 8 === 0, 'length must be a multiple of 8'); // Check each Field input is 8 bits at most if it was not done before at creation time byteChecks && checkBytes(message); - const rate = KECCAK_STATE_LENGTH - capacity; + const rate = KECCAK_STATE_LENGTH_BYTES - capacity; const padded = pad(message, rate, nistVersion); @@ -497,12 +495,7 @@ function nistSha3( message: Field[], byteChecks: boolean = false ): Field[] { - return hash(byteChecks, message, len, 2 * len, true); -} - -// Gadget for Keccak hash function for the parameters used in Ethereum. -function ethereum(message: Field[] = [], byteChecks: boolean = false): Field[] { - return hash(byteChecks, message, 256, 512, false); + return hash(byteChecks, message, len / 8, len / 4, true); } // Gadget for pre-NIST SHA-3 function for output lengths 224/256/384/512. @@ -512,5 +505,10 @@ function preNist( message: Field[], byteChecks: boolean = false ): Field[] { - return hash(byteChecks, message, len, 2 * len, false); + return hash(byteChecks, message, len / 8, len / 4, false); +} + +// Gadget for Keccak hash function for the parameters used in Ethereum. +function ethereum(message: Field[] = [], byteChecks: boolean = false): Field[] { + return preNist(256, message, byteChecks); } From 1003cc538c65f67f7bba914cd2ae0b6132dbe407 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 07:50:03 +0000 Subject: [PATCH 1067/1215] Make Keccak test run for random preimage/digest length --- src/lib/keccak.unit-test.ts | 152 +++++++++++++++--------------------- 1 file changed, 65 insertions(+), 87 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 34e497804e..69da64a7cf 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,139 +1,117 @@ import { Field } from './field.js'; import { Provable } from './provable.js'; import { Keccak } from './keccak.js'; -import { keccak_256, sha3_256, keccak_512, sha3_512 } from '@noble/hashes/sha3'; import { ZkProgram } from './proof_system.js'; import { Random } from './testing/random.js'; import { array, equivalentAsync, fieldWithRng } from './testing/equivalent.js'; import { constraintSystem, contains } from './testing/constraint-system.js'; +import { + keccak_224, + keccak_256, + keccak_384, + keccak_512, + sha3_224, + sha3_256, + sha3_384, + sha3_512, +} from '@noble/hashes/sha3'; -// TODO(jackryanservia): Add test to assert fail for byte that's larger than 255 -// TODO(jackryanservia): Add random length with three runs - -const PREIMAGE_LENGTH = 75; const RUNS = 1; +const testImplementations = { + sha3: { + 224: sha3_224, + 256: sha3_256, + 384: sha3_384, + 512: sha3_512, + }, + preNist: { + 224: keccak_224, + 256: keccak_256, + 384: keccak_384, + 512: keccak_512, + }, +}; + const uint = (length: number) => fieldWithRng(Random.biguint(length)); -const Keccak256 = ZkProgram({ - name: 'keccak256', - publicInput: Provable.Array(Field, PREIMAGE_LENGTH), - publicOutput: Provable.Array(Field, 32), +// Choose a test length at random +const digestLength = [224, 256, 384, 512][Math.floor(Math.random() * 4)] as + | 224 + | 256 + | 384 + | 512; + +// Chose a random preimage length +const preImageLength = digestLength / Math.floor(Math.random() * 4 + 2); + +// No need to test Ethereum because it's just a special case of preNist +const KeccakProgram = ZkProgram({ + name: 'keccak-test', + publicInput: Provable.Array(Field, preImageLength), + publicOutput: Provable.Array(Field, digestLength / 8), methods: { - ethereum: { + nistSha3: { privateInputs: [], method(preImage) { - return Keccak.ethereum(preImage); + return Keccak.nistSha3(digestLength, preImage); }, }, - // No need for preNist Keccak_256 because it's identical to ethereum - nistSha3: { + preNist: { privateInputs: [], method(preImage) { - return Keccak.nistSha3(256, preImage); + return Keccak.preNist(digestLength, preImage); }, }, }, }); -await Keccak256.compile(); +await KeccakProgram.compile(); +// SHA-3 await equivalentAsync( { - from: [array(uint(8), PREIMAGE_LENGTH)], - to: array(uint(8), 32), + from: [array(uint(8), preImageLength)], + to: array(uint(8), digestLength / 8), }, { runs: RUNS } )( (x) => { - const uint8Array = new Uint8Array(x.map(Number)); - const result = keccak_256(uint8Array); + const byteArray = new Uint8Array(x.map(Number)); + const result = testImplementations.sha3[digestLength](byteArray); return Array.from(result).map(BigInt); }, async (x) => { - const proof = await Keccak256.ethereum(x); + const proof = await KeccakProgram.nistSha3(x); + await KeccakProgram.verify(proof); return proof.publicOutput; } ); +// PreNIST Keccak await equivalentAsync( { - from: [array(uint(8), PREIMAGE_LENGTH)], - to: array(uint(8), 32), + from: [array(uint(8), preImageLength)], + to: array(uint(8), digestLength / 8), }, { runs: RUNS } )( (x) => { - const thing = x.map(Number); - const result = sha3_256(new Uint8Array(thing)); + const byteArray = new Uint8Array(x.map(Number)); + const result = testImplementations.preNist[digestLength](byteArray); return Array.from(result).map(BigInt); }, async (x) => { - const proof = await Keccak256.nistSha3(x); + const proof = await KeccakProgram.preNist(x); + await KeccakProgram.verify(proof); return proof.publicOutput; } ); -// const Keccak512 = ZkProgram({ -// name: 'keccak512', -// publicInput: Provable.Array(Field, PREIMAGE_LENGTH), -// publicOutput: Provable.Array(Field, 64), -// methods: { -// preNist: { -// privateInputs: [], -// method(preImage) { -// return Keccak.preNist(512, preImage, 'Big', 'Big', true); -// }, -// }, -// nistSha3: { -// privateInputs: [], -// method(preImage) { -// return Keccak.nistSha3(512, preImage, 'Big', 'Big', true); -// }, -// }, -// }, -// }); - -// await Keccak512.compile(); - -// await equivalentAsync( -// { -// from: [array(uint(8), PREIMAGE_LENGTH)], -// to: array(uint(8), 64), -// }, -// { runs: RUNS } -// )( -// (x) => { -// const uint8Array = new Uint8Array(x.map(Number)); -// const result = keccak_512(uint8Array); -// return Array.from(result).map(BigInt); -// }, -// async (x) => { -// const proof = await Keccak512.preNist(x); -// return proof.publicOutput; -// } +// This takes a while and doesn't do much, so I commented it out +// Constraint system sanity check +// constraintSystem.fromZkProgram( +// KeccakTest, +// 'preNist', +// contains([['Generic'], ['Xor16'], ['Zero'], ['Rot64'], ['RangeCheck0']]) // ); - -// await equivalentAsync( -// { -// from: [array(uint(8), PREIMAGE_LENGTH)], -// to: array(uint(8), 64), -// }, -// { runs: RUNS } -// )( -// (x) => { -// const thing = x.map(Number); -// const result = sha3_512(new Uint8Array(thing)); -// return Array.from(result).map(BigInt); -// }, -// async (x) => { -// const proof = await Keccak512.nistSha3(x); -// return proof.publicOutput; -// } -// ); - -constraintSystem.fromZkProgram( - Keccak256, - 'ethereum', - contains([['Generic'], ['Xor16'], ['Zero'], ['Rot64'], ['RangeCheck0']]) -); From bfb99e25988adab75bbf5e66d1eab05990f1bac9 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 08:30:44 +0000 Subject: [PATCH 1068/1215] Fixes random preImageLength in Keccak unit test --- src/lib/keccak.unit-test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 69da64a7cf..70aa377724 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -42,14 +42,17 @@ const digestLength = [224, 256, 384, 512][Math.floor(Math.random() * 4)] as | 384 | 512; +// Digest length in bytes +const digestLengthBytes = digestLength / 8; + // Chose a random preimage length -const preImageLength = digestLength / Math.floor(Math.random() * 4 + 2); +const preImageLength = Math.floor(digestLength / (Math.random() * 4 + 2)); // No need to test Ethereum because it's just a special case of preNist const KeccakProgram = ZkProgram({ name: 'keccak-test', publicInput: Provable.Array(Field, preImageLength), - publicOutput: Provable.Array(Field, digestLength / 8), + publicOutput: Provable.Array(Field, digestLengthBytes), methods: { nistSha3: { privateInputs: [], @@ -72,7 +75,7 @@ await KeccakProgram.compile(); await equivalentAsync( { from: [array(uint(8), preImageLength)], - to: array(uint(8), digestLength / 8), + to: array(uint(8), digestLengthBytes), }, { runs: RUNS } )( @@ -92,7 +95,7 @@ await equivalentAsync( await equivalentAsync( { from: [array(uint(8), preImageLength)], - to: array(uint(8), digestLength / 8), + to: array(uint(8), digestLengthBytes), }, { runs: RUNS } )( From 898e761767650d34202c814eae48c2e23bb22cd2 Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:01:57 +0000 Subject: [PATCH 1069/1215] Cleans up asserts --- src/lib/keccak.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 55782214f3..5c2e38b5bf 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -119,8 +119,10 @@ const getKeccakStateZeros = (): Field[][] => // Converts a list of bytes to a matrix of Field elements function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { assert( - bytestring.length === 200, - 'improper bytestring length (should be 200)' + bytestring.length === KECCAK_DIM ** 2 * BYTES_PER_WORD, + `improper bytestring length (should be ${ + KECCAK_DIM ** 2 * BYTES_PER_WORD + }})` ); const bytestringArray = Array.from(bytestring); @@ -197,7 +199,7 @@ function bytesToPad(rate: number, length: number): number { // Pads a message M as: // M || pad[x](|M|) -// The padded message will begin with the message and end with the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). +// The padded message will start with the message argument followed by the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). // If nist is true, then the padding rule is 0x06 ..0*..1. // If nist is false, then the padding rule is 10*1. function pad(message: Field[], rate: number, nist: boolean): Field[] { @@ -361,6 +363,15 @@ function absorb( rate: number, rc: bigint[] ): Field[][] { + assert( + rate + capacity === KECCAK_STATE_LENGTH_BYTES, + `invalid rate or capacity (rate + capacity should be ${KECCAK_STATE_LENGTH_BYTES})` + ); + assert( + paddedMessage.length % rate === 0, + 'invalid padded message length (should be multiple of rate)' + ); + let state = getKeccakStateZeros(); // array of capacity zero bytes @@ -370,13 +381,9 @@ function absorb( // split into blocks of rate bits // for each block of rate bits in the padded message -> this is rate bytes const block = paddedMessage.slice(idx, idx + rate); - // pad the block with 0s to up to 200 bytes + // pad the block with 0s to up to KECCAK_STATE_LENGTH_BYTES bytes const paddedBlock = block.concat(zeros); - // padded with zeros each block until they are 200 bytes long - assert( - paddedBlock.length === KECCAK_STATE_LENGTH_BYTES, - `improper Keccak block length (should be ${KECCAK_STATE_LENGTH_BYTES})` - ); + // convert the padded block byte array to a Keccak state const blockState = getKeccakStateOfBytes(paddedBlock); // xor the state with the padded block const stateXor = keccakStateXor(state, blockState); From b45ba97022f1cd31d319df64e9a84d970782fadf Mon Sep 17 00:00:00 2001 From: jackryanservia <90076280+jackryanservia@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:09:33 +0000 Subject: [PATCH 1070/1215] Removes loop in squeeze bc standard length+capacity only does one sequeeze --- src/lib/keccak.ts | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 5c2e38b5bf..7a0ccb7db1 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -395,16 +395,10 @@ function absorb( } // Squeeze state until it has a desired length in bits -function squeeze( - state: Field[][], - length: number, - rate: number, - rc: bigint[] -): Field[] { - let newState = state; - +function squeeze(state: Field[][], length: number, rate: number): Field[] { // number of squeezes const squeezes = Math.floor(length / rate) + 1; + assert(squeezes === 1, 'squeezes should be 1'); // multiple of rate that is larger than output_length, in bytes const outputLength = squeezes * rate; // array with sufficient space to store the output @@ -415,17 +409,6 @@ function squeeze( // copies a section of bytes in the bytestring into the output array outputArray.splice(0, rate, ...outputBytes); - // for the rest of squeezes - for (let i = 1; i < squeezes; i++) { - // apply the permutation function to the state - newState = permutation(newState, rc); - // append the output of the permutation function to the output - const bytestringI = keccakStateToBytes(state); - const outputBytesI = bytestringI.slice(0, rate); - // copies a section of bytes in the bytestring into the output array - outputArray.splice(rate * i, rate, ...outputBytesI); - } - // Obtain the hash selecting the first bitlength bytes of the output array const hashed = outputArray.slice(0, length); return hashed; @@ -450,7 +433,7 @@ function sponge( const state = absorb(paddedMessage, capacity, rate, rc); // squeeze - const hashed = squeeze(state, length, rate, rc); + const hashed = squeeze(state, length, rate); return hashed; } From ec9981da182c2d990c579025e13c922f33c971cb Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 10:51:35 +0100 Subject: [PATCH 1071/1215] fix: remove non-public ecdsa from changelog --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f438d8d0f0..601e2a8b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 -- **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240 - - For an example, see `./src/examples/zkprogram/ecdsa` - `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 - `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 From 5cf87ede20d6d9733842fec9029d27c8c9320585 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 12 Dec 2023 12:07:22 +0100 Subject: [PATCH 1072/1215] Update README-dev.md --- README-dev.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README-dev.md b/README-dev.md index e77b02b95d..9be9eced61 100644 --- a/README-dev.md +++ b/README-dev.md @@ -67,6 +67,12 @@ The following branches are compatible: | | berkeley -> berkeley -> berkeley | | | develop -> develop -> develop | +If you work on o1js, create a feature branch off of one of these base branches. It's encouraged to submit your work in progress as a draft to raise visibility! + +**Default to `main` as the base branch**. + +The other base branches (`berkeley`, `develop`) are only used in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. + ## Run the GitHub actions locally From 9fe14c11e9c002c9c5829b48a5a362e58c0127e3 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 12 Dec 2023 12:08:37 +0100 Subject: [PATCH 1073/1215] Update README-dev.md --- README-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-dev.md b/README-dev.md index 9be9eced61..6c37bfefd2 100644 --- a/README-dev.md +++ b/README-dev.md @@ -67,7 +67,7 @@ The following branches are compatible: | | berkeley -> berkeley -> berkeley | | | develop -> develop -> develop | -If you work on o1js, create a feature branch off of one of these base branches. It's encouraged to submit your work in progress as a draft to raise visibility! +If you work on o1js, create a feature branch off of one of these base branches. It's encouraged to submit your work-in-progress as a draft PR to raise visibility! **Default to `main` as the base branch**. From 00756fc4e92d1a6d9c2411c16b3b35ec8f463ac3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 12:13:35 +0100 Subject: [PATCH 1074/1215] reduce duplication in bytes / word conversion --- src/lib/keccak.ts | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 7a0ccb7db1..bed75174cf 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -97,19 +97,6 @@ const ROUND_CONSTANTS = [ 0x8000000080008008n, ]; -// AUXILARY FUNCTIONS - -// Auxiliary function to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it -function checkBytesToWord(wordBytes: Field[], word: Field): void { - const composition = wordBytes.reduce((acc, value, idx) => { - const shift = 1n << BigInt(8 * idx); - return acc.add(value.mul(shift)); - }, Field.from(0)); - - // Create constraints to check that the word is composed correctly from bytes - word.assertEquals(composition); -} - // KECCAK STATE FUNCTIONS // Return a keccak state where all lanes are equal to 0 @@ -133,12 +120,7 @@ function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { const idx = BYTES_PER_WORD * (KECCAK_DIM * j + i); // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] const wordBytes = bytestringArray.slice(idx, idx + BYTES_PER_WORD); - - for (let k = 0; k < BYTES_PER_WORD; k++) { - // Field element containing value 2^(8*k) - const shift = 1n << BigInt(8 * k); - state[i][j] = state[i][j].add(wordBytes[k].mul(shift)); - } + state[i][j] = bytesToWord(wordBytes); } } return state; @@ -167,7 +149,7 @@ function keccakStateToBytes(state: Field[][]): Field[] { // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] const word_bytes = bytestring.slice(idx, idx + BYTES_PER_WORD); // Assert correct decomposition of bytes from state - checkBytesToWord(word_bytes, state[i][j]); + bytesToWord(word_bytes).assertEquals(state[i][j]); } } return bytestring; @@ -438,12 +420,6 @@ function sponge( return hashed; } -// TODO(jackryanservia): Use lookup argument once issue is resolved -// Checks in the circuit that a list of Fields are at most 8 bits each -function checkBytes(inputs: Field[]): void { - inputs.map(rangeCheck8); -} - // Keccak hash function with input message passed as list of Field bytes. // The message will be parsed as follows: // - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) @@ -502,3 +478,19 @@ function preNist( function ethereum(message: Field[] = [], byteChecks: boolean = false): Field[] { return preNist(256, message, byteChecks); } + +// AUXILARY FUNCTIONS + +// TODO(jackryanservia): Use lookup argument once issue is resolved +// Checks in the circuit that a list of Fields are at most 8 bits each +function checkBytes(inputs: Field[]): void { + inputs.map(rangeCheck8); +} + +// Auxiliary function to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it +function bytesToWord(wordBytes: Field[]): Field { + return wordBytes.reduce((acc, value, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(value.mul(shift)); + }, Field.from(0)); +} From 9ad14b3ce7c30fa8a5d26520cfba542319b54a03 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 12:18:46 +0100 Subject: [PATCH 1075/1215] remove checkBytes argument --- src/lib/keccak.ts | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index bed75174cf..6c6f254cae 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -8,24 +8,16 @@ export { Keccak }; const Keccak = { /** TODO */ - nistSha3( - len: 224 | 256 | 384 | 512, - message: Field[], - byteChecks: boolean = false - ): Field[] { - return nistSha3(len, message, byteChecks); + nistSha3(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + return nistSha3(len, message); }, /** TODO */ - ethereum(message: Field[], byteChecks: boolean = false): Field[] { - return ethereum(message, byteChecks); + ethereum(message: Field[]): Field[] { + return ethereum(message); }, /** TODO */ - preNist( - len: 224 | 256 | 384 | 512, - message: Field[], - byteChecks: boolean = false - ): Field[] { - return preNist(len, message, byteChecks); + preNist(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + return preNist(len, message); }, }; @@ -426,8 +418,7 @@ function sponge( // - the 10*1 pad will take place after the message, until reaching the bit length rate. // - then, {0} pad will take place to finish the 200 bytes of the state. function hash( - byteChecks: boolean = false, - message: Field[] = [], + message: Field[], length: number, capacity: number, nistVersion: boolean @@ -440,9 +431,6 @@ function hash( ); assert(length > 0, 'length must be positive'); - // Check each Field input is 8 bits at most if it was not done before at creation time - byteChecks && checkBytes(message); - const rate = KECCAK_STATE_LENGTH_BYTES - capacity; const padded = pad(message, rate, nistVersion); @@ -456,27 +444,19 @@ function hash( } // Gadget for NIST SHA-3 function for output lengths 224/256/384/512. -function nistSha3( - len: 224 | 256 | 384 | 512, - message: Field[], - byteChecks: boolean = false -): Field[] { - return hash(byteChecks, message, len / 8, len / 4, true); +function nistSha3(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + return hash(message, len / 8, len / 4, true); } // Gadget for pre-NIST SHA-3 function for output lengths 224/256/384/512. // Note that when calling with output length 256 this is equivalent to the ethereum function -function preNist( - len: 224 | 256 | 384 | 512, - message: Field[], - byteChecks: boolean = false -): Field[] { - return hash(byteChecks, message, len / 8, len / 4, false); +function preNist(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + return hash(message, len / 8, len / 4, false); } // Gadget for Keccak hash function for the parameters used in Ethereum. -function ethereum(message: Field[] = [], byteChecks: boolean = false): Field[] { - return preNist(256, message, byteChecks); +function ethereum(message: Field[]): Field[] { + return preNist(256, message); } // AUXILARY FUNCTIONS From dfa64ad7e78744ef63270bfaaf5281568b13fa85 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 12:19:03 +0100 Subject: [PATCH 1076/1215] remove commented code --- src/lib/keccak.unit-test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 70aa377724..f3fc6ab117 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -4,7 +4,6 @@ import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; import { Random } from './testing/random.js'; import { array, equivalentAsync, fieldWithRng } from './testing/equivalent.js'; -import { constraintSystem, contains } from './testing/constraint-system.js'; import { keccak_224, keccak_256, @@ -110,11 +109,3 @@ await equivalentAsync( return proof.publicOutput; } ); - -// This takes a while and doesn't do much, so I commented it out -// Constraint system sanity check -// constraintSystem.fromZkProgram( -// KeccakTest, -// 'preNist', -// contains([['Generic'], ['Xor16'], ['Zero'], ['Rot64'], ['RangeCheck0']]) -// ); From d8a487cf66474853620a92383bc71a6e3dbf7495 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 12:21:01 +0100 Subject: [PATCH 1077/1215] remove unused constant --- src/lib/keccak.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 6c6f254cae..ff836a4aa0 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -41,9 +41,6 @@ const KECCAK_STATE_LENGTH = KECCAK_DIM ** 2 * KECCAK_WORD; // Length of the state in bytes, meaning the 5x5 matrix of words in bytes (200) const KECCAK_STATE_LENGTH_BYTES = KECCAK_STATE_LENGTH / 8; -// Number of rounds of the Keccak permutation function depending on the value `l` (24) -const KECCAK_ROUNDS = 12 + 2 * KECCAK_ELL; - // Creates the 5x5 table of rotation offset for Keccak modulo 64 // | i \ j | 0 | 1 | 2 | 3 | 4 | // | ----- | -- | -- | -- | -- | -- | From 95ef9945fd0f482b1e3d5fa2babda42a62e0a1c0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 12:21:11 +0100 Subject: [PATCH 1078/1215] type tweak --- src/lib/keccak.unit-test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index f3fc6ab117..ead929350e 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -35,11 +35,9 @@ const testImplementations = { const uint = (length: number) => fieldWithRng(Random.biguint(length)); // Choose a test length at random -const digestLength = [224, 256, 384, 512][Math.floor(Math.random() * 4)] as - | 224 - | 256 - | 384 - | 512; +const digestLength = ([224, 256, 384, 512] as const)[ + Math.floor(Math.random() * 4) +]; // Digest length in bytes const digestLengthBytes = digestLength / 8; From cb6756d283b662442527cbfec9a5502f40602a62 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 13:18:07 +0100 Subject: [PATCH 1079/1215] lift bytes handling outside sponge, into hash --- src/lib/keccak.ts | 249 ++++++++++++++++++++++++---------------------- 1 file changed, 129 insertions(+), 120 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index ff836a4aa0..2f8f917595 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,7 +1,7 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; -import { existsOne } from './gadgets/common.js'; +import { exists } from './gadgets/common.js'; import { rangeCheck8 } from './gadgets/range-check.js'; export { Keccak }; @@ -35,8 +35,11 @@ const KECCAK_WORD = 2 ** KECCAK_ELL; // Number of bytes that fit in a word (8) const BYTES_PER_WORD = KECCAK_WORD / 8; +// Length of the state in words, 5x5 = 25 +const KECCAK_STATE_LENGTH_WORDS = KECCAK_DIM ** 2; + // Length of the state in bits, meaning the 5x5 matrix of words in bits (1600) -const KECCAK_STATE_LENGTH = KECCAK_DIM ** 2 * KECCAK_WORD; +const KECCAK_STATE_LENGTH = KECCAK_STATE_LENGTH_WORDS * KECCAK_WORD; // Length of the state in bytes, meaning the 5x5 matrix of words in bytes (200) const KECCAK_STATE_LENGTH_BYTES = KECCAK_STATE_LENGTH / 8; @@ -86,81 +89,6 @@ const ROUND_CONSTANTS = [ 0x8000000080008008n, ]; -// KECCAK STATE FUNCTIONS - -// Return a keccak state where all lanes are equal to 0 -const getKeccakStateZeros = (): Field[][] => - Array.from(Array(KECCAK_DIM), (_) => Array(KECCAK_DIM).fill(Field.from(0))); - -// Converts a list of bytes to a matrix of Field elements -function getKeccakStateOfBytes(bytestring: Field[]): Field[][] { - assert( - bytestring.length === KECCAK_DIM ** 2 * BYTES_PER_WORD, - `improper bytestring length (should be ${ - KECCAK_DIM ** 2 * BYTES_PER_WORD - }})` - ); - - const bytestringArray = Array.from(bytestring); - const state: Field[][] = getKeccakStateZeros(); - - for (let j = 0; j < KECCAK_DIM; j++) { - for (let i = 0; i < KECCAK_DIM; i++) { - const idx = BYTES_PER_WORD * (KECCAK_DIM * j + i); - // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] - const wordBytes = bytestringArray.slice(idx, idx + BYTES_PER_WORD); - state[i][j] = bytesToWord(wordBytes); - } - } - return state; -} - -// Converts a state of Fields to a list of bytes as Fields and creates constraints for it -function keccakStateToBytes(state: Field[][]): Field[] { - const bytestring: Field[] = Array.from( - { length: KECCAK_STATE_LENGTH_BYTES }, - (_, idx) => - existsOne(() => { - // idx = k + 8 * ((dim * j) + i) - const i = Math.floor(idx / BYTES_PER_WORD) % KECCAK_DIM; - const j = Math.floor(idx / BYTES_PER_WORD / KECCAK_DIM); - const k = idx % BYTES_PER_WORD; - const word = state[i][j].toBigInt(); - const byte = (word >> BigInt(8 * k)) & 0xffn; - return byte; - }) - ); - - // Check all words are composed correctly from bytes - for (let j = 0; j < KECCAK_DIM; j++) { - for (let i = 0; i < KECCAK_DIM; i++) { - const idx = BYTES_PER_WORD * (KECCAK_DIM * j + i); - // Create an array containing the 8 bytes starting on idx that correspond to the word in [i,j] - const word_bytes = bytestring.slice(idx, idx + BYTES_PER_WORD); - // Assert correct decomposition of bytes from state - bytesToWord(word_bytes).assertEquals(state[i][j]); - } - } - return bytestring; -} - -// XOR two states together and return the result -function keccakStateXor(a: Field[][], b: Field[][]): Field[][] { - assert( - a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, - `invalid \`a\` dimensions (should be ${KECCAK_DIM})` - ); - assert( - b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, - `invalid \`b\` dimensions (should be ${KECCAK_DIM})` - ); - - // Calls Gadgets.xor on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix - return a.map((row, i) => - row.map((value, j) => Gadgets.xor(value, b[i][j], 64)) - ); -} - // KECCAK HASH FUNCTION // Computes the number of required extra bytes to pad a message of length bytes @@ -252,7 +180,7 @@ const theta = (state: Field[][]): Field[][] => { // We use the first index of the state array as the i coordinate and the second index as the j coordinate. function piRho(state: Field[][]): Field[][] { const stateE = state; - const stateB: Field[][] = getKeccakStateZeros(); + const stateB = State.zeros(); // for all i in {0..4} and j in {0..4}: B[y,2x+3y] = ROT(E[i,j], r[i,j]) for (let i = 0; i < KECCAK_DIM; i++) { @@ -278,7 +206,7 @@ function piRho(state: Field[][]): Field[][] { // end for function chi(state: Field[][]): Field[][] { const stateB = state; - const stateF = getKeccakStateZeros(); + const stateF = State.zeros(); // for all i in {0..4} and j in {0..4}: F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) for (let i = 0; i < KECCAK_DIM; i++) { @@ -333,17 +261,17 @@ function absorb( capacity: number, rate: number, rc: bigint[] -): Field[][] { +): State { assert( - rate + capacity === KECCAK_STATE_LENGTH_BYTES, - `invalid rate or capacity (rate + capacity should be ${KECCAK_STATE_LENGTH_BYTES})` + rate + capacity === KECCAK_STATE_LENGTH_WORDS, + `invalid rate or capacity (rate + capacity should be ${KECCAK_STATE_LENGTH_WORDS})` ); assert( paddedMessage.length % rate === 0, 'invalid padded message length (should be multiple of rate)' ); - let state = getKeccakStateZeros(); + let state = State.zeros(); // array of capacity zero bytes const zeros = Array(capacity).fill(Field.from(0)); @@ -352,36 +280,29 @@ function absorb( // split into blocks of rate bits // for each block of rate bits in the padded message -> this is rate bytes const block = paddedMessage.slice(idx, idx + rate); - // pad the block with 0s to up to KECCAK_STATE_LENGTH_BYTES bytes + // pad the block with 0s to up to KECCAK_STATE_LENGTH_WORDS words const paddedBlock = block.concat(zeros); - // convert the padded block byte array to a Keccak state - const blockState = getKeccakStateOfBytes(paddedBlock); + // convert the padded block to a Keccak state + const blockState = State.fromWords(paddedBlock); // xor the state with the padded block - const stateXor = keccakStateXor(state, blockState); + const stateXor = State.xor(state, blockState); // apply the permutation function to the xored state - const statePerm = permutation(stateXor, rc); - state = statePerm; + state = permutation(stateXor, rc); } return state; } -// Squeeze state until it has a desired length in bits -function squeeze(state: Field[][], length: number, rate: number): Field[] { +// Squeeze state until it has a desired length in words +function squeeze(state: State, length: number, rate: number): Field[] { // number of squeezes const squeezes = Math.floor(length / rate) + 1; assert(squeezes === 1, 'squeezes should be 1'); - // multiple of rate that is larger than output_length, in bytes - const outputLength = squeezes * rate; - // array with sufficient space to store the output - const outputArray = Array(outputLength).fill(Field.from(0)); + // first state to be squeezed - const bytestring = keccakStateToBytes(state); - const outputBytes = bytestring.slice(0, rate); - // copies a section of bytes in the bytestring into the output array - outputArray.splice(0, rate, ...outputBytes); + const words = State.toWords(state); - // Obtain the hash selecting the first bitlength bytes of the output array - const hashed = outputArray.slice(0, length); + // Obtain the hash selecting the first `length` words of the output array + const hashed = words.slice(0, length); return hashed; } @@ -393,15 +314,10 @@ function sponge( rate: number ): Field[] { // check that the padded message is a multiple of rate - if (paddedMessage.length % rate !== 0) { - throw new Error('Invalid padded message length'); - } - - // load round constants - const rc = ROUND_CONSTANTS; + assert(paddedMessage.length % rate === 0, 'Invalid padded message length'); // absorb - const state = absorb(paddedMessage, capacity, rate, rc); + const state = absorb(paddedMessage, capacity, rate, ROUND_CONSTANTS); // squeeze const hashed = squeeze(state, length, rate); @@ -428,16 +344,22 @@ function hash( ); assert(length > 0, 'length must be positive'); - const rate = KECCAK_STATE_LENGTH_BYTES - capacity; + // convert capacity and length to word units + assert(capacity % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + capacity /= BYTES_PER_WORD; + assert(length % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + length /= BYTES_PER_WORD; - const padded = pad(message, rate, nistVersion); + const rate = KECCAK_STATE_LENGTH_WORDS - capacity; - const hash = sponge(padded, length, capacity, rate); + // apply padding, convert to words, and hash + const paddedBytes = pad(message, rate * BYTES_PER_WORD, nistVersion); + const padded = bytesToWords(paddedBytes); - // Always check each Field output is 8 bits at most because they are created here - checkBytes(hash); + const hash = sponge(padded, length, capacity, rate); + const hashBytes = wordsToBytes(hash); - return hash; + return hashBytes; } // Gadget for NIST SHA-3 function for output lengths 224/256/384/512. @@ -456,18 +378,105 @@ function ethereum(message: Field[]): Field[] { return preNist(256, message); } +// FUNCTIONS ON KECCAK STATE + +type State = Field[][]; +const State = { + /** + * Create a state of all zeros + */ + zeros(): State { + return Array.from(Array(KECCAK_DIM), (_) => + Array(KECCAK_DIM).fill(Field.from(0)) + ); + }, + + /** + * Flatten state to words + */ + toWords(state: State): Field[] { + const words = Array(KECCAK_STATE_LENGTH_WORDS); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + words[KECCAK_DIM * j + i] = state[i][j]; + } + } + return words; + }, + + /** + * Compose words to state + */ + fromWords(words: Field[]): State { + const state = State.zeros(); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + state[i][j] = words[KECCAK_DIM * j + i]; + } + } + return state; + }, + + /** + * XOR two states together and return the result + */ + xor(a: State, b: State): State { + assert( + a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, + `invalid \`a\` dimensions (should be ${KECCAK_DIM})` + ); + assert( + b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, + `invalid \`b\` dimensions (should be ${KECCAK_DIM})` + ); + + // Calls Gadgets.xor on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix + return a.map((row, i) => + row.map((value, j) => Gadgets.xor(value, b[i][j], 64)) + ); + }, +}; + // AUXILARY FUNCTIONS -// TODO(jackryanservia): Use lookup argument once issue is resolved -// Checks in the circuit that a list of Fields are at most 8 bits each -function checkBytes(inputs: Field[]): void { - inputs.map(rangeCheck8); -} +// Auxiliary functions to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it -// Auxiliary function to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it function bytesToWord(wordBytes: Field[]): Field { return wordBytes.reduce((acc, value, idx) => { const shift = 1n << BigInt(8 * idx); return acc.add(value.mul(shift)); }, Field.from(0)); } + +function wordToBytes(word: Field): Field[] { + let bytes = exists(BYTES_PER_WORD, () => { + let w = word.toBigInt(); + return Array.from( + { length: BYTES_PER_WORD }, + (_, k) => (w >> BigInt(8 * k)) & 0xffn + ); + }); + // range-check + // TODO(jackryanservia): Use lookup argument once issue is resolved + bytes.forEach(rangeCheck8); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +function bytesToWords(bytes: Field[]): Field[] { + return chunk(bytes, BYTES_PER_WORD).map(bytesToWord); +} + +function wordsToBytes(words: Field[]): Field[] { + return words.flatMap(wordToBytes); +} + +function chunk(array: T[], size: number): T[][] { + assert(array.length % size === 0, 'invalid input length'); + return Array.from({ length: array.length / size }, (_, i) => + array.slice(size * i, size * (i + 1)) + ); +} From 8300ac15f897ea9b9ebb7509d0eed114680cf4c5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 13:52:26 +0100 Subject: [PATCH 1080/1215] don't support 224 for now because it needs extra work to pad to full words --- src/lib/keccak.ts | 41 +++++++++++++++++++++++++++++++------ src/lib/keccak.unit-test.ts | 4 +--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 2f8f917595..5c9bcfc77a 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -8,7 +8,7 @@ export { Keccak }; const Keccak = { /** TODO */ - nistSha3(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + nistSha3(len: 256 | 384 | 512, message: Field[]): Field[] { return nistSha3(len, message); }, /** TODO */ @@ -16,9 +16,38 @@ const Keccak = { return ethereum(message); }, /** TODO */ - preNist(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { + preNist(len: 256 | 384 | 512, message: Field[]): Field[] { return preNist(len, message); }, + + /** + * Low-level API to access Keccak operating on 64-bit words. + * + * @example + * ```ts + * const message = [0x02, 0x03, 0x04, 0x05].map(Field.from); + * const padded = Keccak.LowLevel.padMessage(message, 256, false); + * const hash = Keccak.LowLevel.hash(padded, 256); + * // hash is an array of 4 Field elements of 64 bits each + * ``` + */ + LowLevel: { + hash(paddedMessage: Field[], length: 256 | 384 | 512): Field[] { + length /= 64; + let capacity = length * 2; + let rate = 25 - capacity; + return sponge(paddedMessage, length, capacity, rate); + }, + + padMessage( + message: Field[], + length: 256 | 384 | 512, + isNist: boolean + ): Field[] { + let paddedBytes = pad(message, 200 - length / 4, isNist); + return bytesToWords(paddedBytes); + }, + }, }; // KECCAK CONSTANTS @@ -362,14 +391,14 @@ function hash( return hashBytes; } -// Gadget for NIST SHA-3 function for output lengths 224/256/384/512. -function nistSha3(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { +// Gadget for NIST SHA-3 function for output lengths 256/384/512. +function nistSha3(len: 256 | 384 | 512, message: Field[]): Field[] { return hash(message, len / 8, len / 4, true); } -// Gadget for pre-NIST SHA-3 function for output lengths 224/256/384/512. +// Gadget for pre-NIST SHA-3 function for output lengths 256/384/512. // Note that when calling with output length 256 this is equivalent to the ethereum function -function preNist(len: 224 | 256 | 384 | 512, message: Field[]): Field[] { +function preNist(len: 256 | 384 | 512, message: Field[]): Field[] { return hash(message, len / 8, len / 4, false); } diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index ead929350e..7caa5c166a 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -35,9 +35,7 @@ const testImplementations = { const uint = (length: number) => fieldWithRng(Random.biguint(length)); // Choose a test length at random -const digestLength = ([224, 256, 384, 512] as const)[ - Math.floor(Math.random() * 4) -]; +const digestLength = ([256, 384, 512] as const)[Math.floor(Math.random() * 4)]; // Digest length in bytes const digestLengthBytes = digestLength / 8; From e9ec0e8b9b8a8aa4eb0cc3e50d8d7596dbeed208 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 13:52:34 +0100 Subject: [PATCH 1081/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1e14e50fe2..c09afcd2fc 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1e14e50fe270e8b401e88351b2c03a6e2ab5fa63 +Subproject commit c09afcd2fc902abb3db7c029740d401414f76f8b From 25bd83bc2a8053efe3c09dc2cbef119d84de1019 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 15:22:16 +0100 Subject: [PATCH 1082/1215] add keccak to ecdsa circuit --- src/lib/foreign-ecdsa.ts | 80 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index 235e284556..a47c8142f1 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -8,9 +8,12 @@ import { toPoint, } from './foreign-curve.js'; import { AlmostForeignField } from './foreign-field.js'; -import { assert } from './gadgets/common.js'; +import { assert, witnessSlice } from './gadgets/common.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Ecdsa } from './gadgets/elliptic-curve.js'; +import { Field } from './field.js'; +import { TupleN } from './util/types.js'; +import { rangeCheck64 } from './gadgets/range-check.js'; // external API export { createEcdsa, EcdsaSignature }; @@ -90,11 +93,14 @@ class EcdsaSignature { * let sig = Provable.witness(Ecdsa.provable, () => signature); * * // verify signature - * let isValid = sig.verify(msgHash, pk); + * let isValid = sig.verifySignedHash(msgHash, pk); * isValid.assertTrue('signature verifies'); * ``` */ - verify(msgHash: AlmostForeignField | bigint, publicKey: FlexiblePoint) { + verifySignedHash( + msgHash: AlmostForeignField | bigint, + publicKey: FlexiblePoint + ) { let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); let publicKey_ = this.Constructor.Curve.from(publicKey); return Ecdsa.verify( @@ -170,3 +176,71 @@ function createEcdsa( function toObject(signature: EcdsaSignature) { return { r: signature.r.value, s: signature.s.value }; } + +/** + * Provable method to convert keccak256 hash output to ECDSA scalar = "message hash" + * + * Spec from [Wikipedia](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm): + * + * > Let z be the L_n leftmost bits of e, where L_{n} is the bit length of the group order n. + * > (Note that z can be greater than n but not longer.) + * + * The output z is used as input to a multiplication: + * + * > Calculate u_1 = z s^(-1) mod n ... + * + * That means we don't need to reduce z mod n: The fact that it has bitlength <= n makes it + * almost reduced which is enough for the multiplication to be correct. + * (using a weaker notion of "almost reduced" than what we usually prove, but sufficient for all uses of it: `z < 2^ceil(log(n))`) + * + * In summary, this method: + * - takes 256 bits in 4x64 form + * - converts them to 3 limbs which collectively have L_n <= 256 bits, + * by dropping the higher bits. + */ +function keccakOutputToLimbs( + hash: TupleN, + Curve: typeof ForeignCurve +) { + const L_n = Curve.Scalar.sizeInBits; + // keep it simple, avoid dealing with bits dropped from words other than the highest + assert(L_n > 3 * 64, `Scalar sizes ${L_n} <= ${3 * 64} not supported`); + + // TODO confirm endianness + let [w0, w1, w2, w3] = hash; + + // split w1 and w2 along the 88 bit boundaries + let [w10, w11] = split64(w1, 24); // 24 = 88 - 64; 40 = 64 - 24 + let [w20, w21] = split64(w2, 48); // 48 = 88 - 40; 16 = 64 - 48 + + // if L_n < 256, drop higher part of w3 so that the total length is L_n + if (L_n < 256) { + let [w30] = split64(w3, L_n - 3 * 64); + w3 = w30; + } + + // piece together into limbs + let x0 = w0.add(w10.mul(1n << 64n)); + let x1 = w11.add(w20.mul(1n << 40n)); + let x2 = w21.add(w3.mul(1n << 16n)); + + return new Curve.Scalar.AlmostReduced([x0, x1, x2]); +} + +// split 64-bit field into two pieces of lengths n and 64-n +function split64(x: Field, n: number) { + let x0 = witnessSlice(x, 0, n); + let x1 = witnessSlice(x, n, 64); + + // prove decomposition + let nn = BigInt(n); + x0.add(x1.mul(1n << nn)).assertEquals(x); + + // prove ranges: x0 in [0, 2^n), x1 in [0, 2^(64-n)) + rangeCheck64(x0); + rangeCheck64(x0.mul(1n << (64n - nn))); + rangeCheck64(x1); + // note: x1 < 2^(64-n) is implied by x0 + x1 * 2^n = x < 2^64 + + return [x0, x1]; +} From a5ca0b396174f0e3b6647880706c74adc3f81f80 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 16:32:54 +0100 Subject: [PATCH 1083/1215] change conversion to take bytes --- src/lib/foreign-ecdsa.ts | 69 ++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index a47c8142f1..c894b881af 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -8,12 +8,11 @@ import { toPoint, } from './foreign-curve.js'; import { AlmostForeignField } from './foreign-field.js'; -import { assert, witnessSlice } from './gadgets/common.js'; +import { assert } from './gadgets/common.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Ecdsa } from './gadgets/elliptic-curve.js'; import { Field } from './field.js'; -import { TupleN } from './util/types.js'; -import { rangeCheck64 } from './gadgets/range-check.js'; +import { l } from './gadgets/range-check.js'; // external API export { createEcdsa, EcdsaSignature }; @@ -193,54 +192,34 @@ function toObject(signature: EcdsaSignature) { * almost reduced which is enough for the multiplication to be correct. * (using a weaker notion of "almost reduced" than what we usually prove, but sufficient for all uses of it: `z < 2^ceil(log(n))`) * - * In summary, this method: - * - takes 256 bits in 4x64 form - * - converts them to 3 limbs which collectively have L_n <= 256 bits, - * by dropping the higher bits. + * In summary, this method just: + * - takes a 32 bytes hash + * - converts them to 3 limbs which collectively have L_n <= 256 bits */ -function keccakOutputToLimbs( - hash: TupleN, - Curve: typeof ForeignCurve -) { +function keccakOutputToLimbs(hash: Field[], Curve: typeof ForeignCurve) { const L_n = Curve.Scalar.sizeInBits; - // keep it simple, avoid dealing with bits dropped from words other than the highest - assert(L_n > 3 * 64, `Scalar sizes ${L_n} <= ${3 * 64} not supported`); - - // TODO confirm endianness - let [w0, w1, w2, w3] = hash; - - // split w1 and w2 along the 88 bit boundaries - let [w10, w11] = split64(w1, 24); // 24 = 88 - 64; 40 = 64 - 24 - let [w20, w21] = split64(w2, 48); // 48 = 88 - 40; 16 = 64 - 48 - - // if L_n < 256, drop higher part of w3 so that the total length is L_n - if (L_n < 256) { - let [w30] = split64(w3, L_n - 3 * 64); - w3 = w30; - } + // keep it simple for now, avoid dealing with dropping bits + // TODO: what does "leftmost bits" mean? big-endian or little-endian? + // @noble/curves uses a right shift, dropping the least significant bits: + // https://github.com/paulmillr/noble-curves/blob/4007ee975bcc6410c2e7b504febc1d5d625ed1a4/src/abstract/weierstrass.ts#L933 + assert(L_n === 256, `Scalar sizes ${L_n} !== 256 not supported`); + assert(hash.length === 32, `hash length ${hash.length} !== 32 not supported`); // piece together into limbs - let x0 = w0.add(w10.mul(1n << 64n)); - let x1 = w11.add(w20.mul(1n << 40n)); - let x2 = w21.add(w3.mul(1n << 16n)); + // bytes are big-endian, so the first byte is the most significant + assert(l === 88n); + let x2 = bytesToLimbBE(hash.slice(0, 10)); + let x1 = bytesToLimbBE(hash.slice(10, 21)); + let x0 = bytesToLimbBE(hash.slice(21, 32)); return new Curve.Scalar.AlmostReduced([x0, x1, x2]); } -// split 64-bit field into two pieces of lengths n and 64-n -function split64(x: Field, n: number) { - let x0 = witnessSlice(x, 0, n); - let x1 = witnessSlice(x, n, 64); - - // prove decomposition - let nn = BigInt(n); - x0.add(x1.mul(1n << nn)).assertEquals(x); - - // prove ranges: x0 in [0, 2^n), x1 in [0, 2^(64-n)) - rangeCheck64(x0); - rangeCheck64(x0.mul(1n << (64n - nn))); - rangeCheck64(x1); - // note: x1 < 2^(64-n) is implied by x0 + x1 * 2^n = x < 2^64 - - return [x0, x1]; +function bytesToLimbBE(bytes: Field[]) { + let n = bytes.length; + let limb = bytes[0]; + for (let i = 1; i < n; i++) { + limb = limb.mul(1n << 8n).add(bytes[i]); + } + return limb.seal(); } From fa5861d86da568e47072d8e4398cd89923520eca Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 16:54:06 +0100 Subject: [PATCH 1084/1215] use keccak in ECDSA verify / sign --- src/lib/foreign-ecdsa.ts | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index c894b881af..d5d941745c 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -13,6 +13,7 @@ import { Field3 } from './gadgets/foreign-field.js'; import { Ecdsa } from './gadgets/elliptic-curve.js'; import { Field } from './field.js'; import { l } from './gadgets/range-check.js'; +import { Keccak } from './keccak.js'; // external API export { createEcdsa, EcdsaSignature }; @@ -65,7 +66,7 @@ class EcdsaSignature { } /** - * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * Verify the ECDSA signature given the message (an array of bytes) and public key (a {@link Curve} point). * * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. * So, to actually prove validity of a signature, you need to assert that the result is true. @@ -79,23 +80,38 @@ class EcdsaSignature { * class Scalar extends Secp256k1.Scalar {} * class Ecdsa extends createEcdsa(Secp256k1) {} * + * let message = 'my message'; + * let messageBytes = new TextEncoder().encode(message); + * * // outside provable code: create inputs * let privateKey = Scalar.random(); * let publicKey = Secp256k1.generator.scale(privateKey); - * let messageHash = Scalar.random(); - * let signature = Ecdsa.sign(messageHash.toBigInt(), privateKey.toBigInt()); + * let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); * * // ... * // in provable code: create input witnesses (or use method inputs, or constants) * let pk = Provable.witness(Secp256k1.provable, () => publicKey); - * let msgHash = Provable.witness(Scalar.Canonical.provable, () => messageHash); + * let msg = Provable.witness(Provable.Array(Field, 9), () => messageBytes.map(Field)); * let sig = Provable.witness(Ecdsa.provable, () => signature); * * // verify signature - * let isValid = sig.verifySignedHash(msgHash, pk); + * let isValid = sig.verify(msg, pk); * isValid.assertTrue('signature verifies'); * ``` */ + verify(message: Field[], publicKey: FlexiblePoint) { + let msgHashBytes = Keccak.ethereum(message); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); + return this.verifySignedHash(msgHash, publicKey); + } + + /** + * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * + * This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + */ verifySignedHash( msgHash: AlmostForeignField | bigint, publicKey: FlexiblePoint @@ -110,12 +126,28 @@ class EcdsaSignature { ); } + /** + * Create an {@link EcdsaSignature} by signing a message with a private key. + * + * Note: This method is not provable, and only takes JS bigints as input. + */ + static sign(message: (bigint | number)[] | Uint8Array, privateKey: bigint) { + let msgFields = [...message].map(Field.from); + let msgHashBytes = Keccak.ethereum(msgFields); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Curve); + return this.signHash(msgHash.toBigInt(), privateKey); + } + /** * Create an {@link EcdsaSignature} by signing a message hash with a private key. * + * This is a building block of {@link EcdsaSignature.sign}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + * * Note: This method is not provable, and only takes JS bigints as input. */ - static sign(msgHash: bigint, privateKey: bigint) { + static signHash(msgHash: bigint, privateKey: bigint) { let { r, s } = Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); return new this({ r, s }); } @@ -196,7 +228,7 @@ function toObject(signature: EcdsaSignature) { * - takes a 32 bytes hash * - converts them to 3 limbs which collectively have L_n <= 256 bits */ -function keccakOutputToLimbs(hash: Field[], Curve: typeof ForeignCurve) { +function keccakOutputToScalar(hash: Field[], Curve: typeof ForeignCurve) { const L_n = Curve.Scalar.sizeInBits; // keep it simple for now, avoid dealing with dropping bits // TODO: what does "leftmost bits" mean? big-endian or little-endian? From ff7770b6ac991da6f16eb905a101192604869d6b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 17:12:07 +0100 Subject: [PATCH 1085/1215] fix: support out of circuit call --- src/lib/keccak.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 5c9bcfc77a..da64a42694 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,8 +1,8 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; -import { exists } from './gadgets/common.js'; import { rangeCheck8 } from './gadgets/range-check.js'; +import { Provable } from './provable.js'; export { Keccak }; @@ -478,11 +478,10 @@ function bytesToWord(wordBytes: Field[]): Field { } function wordToBytes(word: Field): Field[] { - let bytes = exists(BYTES_PER_WORD, () => { + let bytes = Provable.witness(Provable.Array(Field, BYTES_PER_WORD), () => { let w = word.toBigInt(); - return Array.from( - { length: BYTES_PER_WORD }, - (_, k) => (w >> BigInt(8 * k)) & 0xffn + return Array.from({ length: BYTES_PER_WORD }, (_, k) => + Field.from((w >> BigInt(8 * k)) & 0xffn) ); }); // range-check From b7ecd5be709c9f06d3cec91bb718286439202ce7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 17:12:23 +0100 Subject: [PATCH 1086/1215] use keccak in example --- src/examples/crypto/ecdsa/ecdsa.ts | 45 ++++++++++++++++++++++++++---- src/examples/crypto/ecdsa/run.ts | 32 +++++++++++---------- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index 8268117033..0e2987fe43 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -1,21 +1,54 @@ -import { ZkProgram, Crypto, createEcdsa, createForeignCurve, Bool } from 'o1js'; +import assert from 'assert'; +import { + ZkProgram, + Crypto, + createEcdsa, + createForeignCurve, + Bool, + Struct, + Provable, + Field, +} from 'o1js'; -export { ecdsaProgram, Secp256k1, Ecdsa }; +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Message }; class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} class Scalar extends Secp256k1.Scalar {} class Ecdsa extends createEcdsa(Secp256k1) {} -const ecdsaProgram = ZkProgram({ +// a message of 8 bytes +class Message extends Struct({ array: Provable.Array(Field, 8) }) { + static from(message: Uint8Array) { + assert(message.length === 8, 'message must be 8 bytes'); + return new Message({ array: [...message].map(Field) }); + } +} + +const keccakAndEcdsa = ZkProgram({ name: 'ecdsa', + publicInput: Message, + publicOutput: Bool, + + methods: { + verify: { + privateInputs: [Ecdsa.provable, Secp256k1.provable], + method(message: Message, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verify(message.array, publicKey); + }, + }, + }, +}); + +const ecdsa = ZkProgram({ + name: 'ecdsa-only', publicInput: Scalar.provable, publicOutput: Bool, methods: { - verifyEcdsa: { + verifySignedHash: { privateInputs: [Ecdsa.provable, Secp256k1.provable], - method(msgHash: Scalar, signature: Ecdsa, publicKey: Secp256k1) { - return signature.verify(msgHash, publicKey); + method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verifySignedHash(message, publicKey); }, }, }, diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts index a2e49f7062..f4983cbc80 100644 --- a/src/examples/crypto/ecdsa/run.ts +++ b/src/examples/crypto/ecdsa/run.ts @@ -1,4 +1,4 @@ -import { Secp256k1, Ecdsa, ecdsaProgram } from './ecdsa.js'; +import { Secp256k1, Ecdsa, keccakAndEcdsa, Message, ecdsa } from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature @@ -6,28 +6,32 @@ import assert from 'assert'; let privateKey = Secp256k1.Scalar.random(); let publicKey = Secp256k1.generator.scale(privateKey); -// TODO use an actual keccak hash -let messageHash = Secp256k1.Scalar.random(); +let messageBytes = new TextEncoder().encode('whats up'); -let signature = Ecdsa.sign(messageHash.toBigInt(), privateKey.toBigInt()); +let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); // investigate the constraint system generated by ECDSA verify -console.time('ecdsa verify (build constraint system)'); -let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; -console.timeEnd('ecdsa verify (build constraint system)'); +console.time('ecdsa verify only (build constraint system)'); +let csOnly = ecdsa.analyzeMethods().verifySignedHash; +console.timeEnd('ecdsa verify only (build constraint system)'); +console.log(csOnly.summary()); +console.time('keccak + ecdsa verify (build constraint system)'); +let cs = keccakAndEcdsa.analyzeMethods().verify; +console.timeEnd('keccak + ecdsa verify (build constraint system)'); console.log(cs.summary()); // compile and prove -console.time('ecdsa verify (compile)'); -await ecdsaProgram.compile(); -console.timeEnd('ecdsa verify (compile)'); +console.time('keccak + ecdsa verify (compile)'); +await keccakAndEcdsa.compile(); +console.timeEnd('keccak + ecdsa verify (compile)'); -console.time('ecdsa verify (prove)'); -let proof = await ecdsaProgram.verifyEcdsa(messageHash, signature, publicKey); -console.timeEnd('ecdsa verify (prove)'); +console.time('keccak + ecdsa verify (prove)'); +let message = Message.from(messageBytes); +let proof = await keccakAndEcdsa.verify(message, signature, publicKey); +console.timeEnd('keccak + ecdsa verify (prove)'); proof.publicOutput.assertTrue('signature verifies'); -assert(await ecdsaProgram.verify(proof), 'proof verifies'); +assert(await keccakAndEcdsa.verify(proof), 'proof verifies'); From a664fa3104fd60abd8e75937228e569446d98fcb Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 17:54:41 +0100 Subject: [PATCH 1087/1215] expose keccak --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index bbe7d8bb74..9f63303560 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,8 @@ export { export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Keccak } from './lib/keccak.js'; + export * from './lib/signature.js'; export type { ProvableExtended, From a041bc9bd343f97c7e0f3a1e2f0357299c297210 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 17:55:02 +0100 Subject: [PATCH 1088/1215] change example to do ecdsa and keccak together and separately --- src/examples/crypto/ecdsa/ecdsa.ts | 56 +++++++++++++++++++++++------- src/examples/crypto/ecdsa/run.ts | 20 ++++++----- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index 0e2987fe43..cf2407df35 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { ZkProgram, Crypto, @@ -8,34 +7,37 @@ import { Struct, Provable, Field, + Keccak, + Gadgets, } from 'o1js'; -export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Message }; +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Message32 }; class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} class Scalar extends Secp256k1.Scalar {} class Ecdsa extends createEcdsa(Secp256k1) {} - -// a message of 8 bytes -class Message extends Struct({ array: Provable.Array(Field, 8) }) { - static from(message: Uint8Array) { - assert(message.length === 8, 'message must be 8 bytes'); - return new Message({ array: [...message].map(Field) }); - } -} +class Message32 extends Message(32) {} const keccakAndEcdsa = ZkProgram({ name: 'ecdsa', - publicInput: Message, + publicInput: Message32, publicOutput: Bool, methods: { - verify: { + verifyEcdsa: { privateInputs: [Ecdsa.provable, Secp256k1.provable], - method(message: Message, signature: Ecdsa, publicKey: Secp256k1) { + method(message: Message32, signature: Ecdsa, publicKey: Secp256k1) { return signature.verify(message.array, publicKey); }, }, + + sha3: { + privateInputs: [], + method(message: Message32) { + Keccak.nistSha3(256, message.array); + return Bool(true); + }, + }, }, }); @@ -53,3 +55,31 @@ const ecdsa = ZkProgram({ }, }, }); + +// helper: class for a message of n bytes + +function Message(lengthInBytes: number) { + return class Message extends Struct({ + array: Provable.Array(Field, lengthInBytes), + }) { + static from(message: string | Uint8Array) { + if (typeof message === 'string') { + message = new TextEncoder().encode(message); + } + let padded = new Uint8Array(32); + padded.set(message); + return new this({ array: [...padded].map(Field) }); + } + + toBytes() { + return Uint8Array.from(this.array.map((f) => Number(f))); + } + + /** + * Important: check that inputs are, in fact, bytes + */ + static check(msg: { array: Field[] }) { + msg.array.forEach(Gadgets.rangeCheck8); + } + }; +} diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts index f4983cbc80..377e18d50a 100644 --- a/src/examples/crypto/ecdsa/run.ts +++ b/src/examples/crypto/ecdsa/run.ts @@ -1,4 +1,4 @@ -import { Secp256k1, Ecdsa, keccakAndEcdsa, Message, ecdsa } from './ecdsa.js'; +import { Secp256k1, Ecdsa, keccakAndEcdsa, Message32, ecdsa } from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature @@ -6,19 +6,24 @@ import assert from 'assert'; let privateKey = Secp256k1.Scalar.random(); let publicKey = Secp256k1.generator.scale(privateKey); -let messageBytes = new TextEncoder().encode('whats up'); +let message = Message32.from("what's up"); -let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); // investigate the constraint system generated by ECDSA verify console.time('ecdsa verify only (build constraint system)'); -let csOnly = ecdsa.analyzeMethods().verifySignedHash; +let csEcdsa = ecdsa.analyzeMethods().verifySignedHash; console.timeEnd('ecdsa verify only (build constraint system)'); -console.log(csOnly.summary()); +console.log(csEcdsa.summary()); + +console.time('keccak only (build constraint system)'); +let csKeccak = keccakAndEcdsa.analyzeMethods().sha3; +console.timeEnd('keccak only (build constraint system)'); +console.log(csKeccak.summary()); console.time('keccak + ecdsa verify (build constraint system)'); -let cs = keccakAndEcdsa.analyzeMethods().verify; +let cs = keccakAndEcdsa.analyzeMethods().verifyEcdsa; console.timeEnd('keccak + ecdsa verify (build constraint system)'); console.log(cs.summary()); @@ -29,8 +34,7 @@ await keccakAndEcdsa.compile(); console.timeEnd('keccak + ecdsa verify (compile)'); console.time('keccak + ecdsa verify (prove)'); -let message = Message.from(messageBytes); -let proof = await keccakAndEcdsa.verify(message, signature, publicKey); +let proof = await keccakAndEcdsa.verifyEcdsa(message, signature, publicKey); console.timeEnd('keccak + ecdsa verify (prove)'); proof.publicOutput.assertTrue('signature verifies'); From 9c68226d81333a892b57b4e1ffc6ac524a6b392c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 18:02:35 +0100 Subject: [PATCH 1089/1215] vk regression --- tests/vk-regression/vk-regression.json | 21 +++++++++++++++++++-- tests/vk-regression/vk-regression.ts | 8 ++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index ee63f91f3a..bd1167cb56 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -202,10 +202,10 @@ "hash": "" } }, - "ecdsa": { + "ecdsa-only": { "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", "methods": { - "verifyEcdsa": { + "verifySignedHash": { "rows": 38888, "digest": "f75dd9e49c88eb6097a7f3abbe543467" } @@ -214,5 +214,22 @@ "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAHV05f+qac/ZBDmwqzaprv0J0hiho1m+s3yNkKQVSOkFyy3T9xnMBFjK62dF1KOp2k1Uvadd2KRyqwXiGN7JtQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09Ag9D9DKKoexOqr3N/Z3GGptvh3qvOPyxcWf475b+B/fTIwTQQC8ykkZ35HAVW3ZT6XDz0QFSmB0NJ8A+lkaTa0JF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" } + }, + "ecdsa": { + "digest": "2c5e8b21a7b92454614abcf51fb039271f617767d53c11a28e0967ff8f7eda4b", + "methods": { + "sha3": { + "rows": 14660, + "digest": "80edbd7c3b18635749d8ea72c7c3fa25" + }, + "verifyEcdsa": { + "rows": 53552, + "digest": "19e415d0e19f292917fa6aa81133ac87" + } + }, + "verificationKey": { + "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAGGpFLTVX/ZRNJvF4z8+jcQhWnwFvV1AulW8zoTvLrYluoItNz+OoFFO8QH5STv4bv1eLYKwvb8kzQKsRREZEBnH+2W6nrDuUAbAB4SoKC1kPYvdJAEGC5FgXIA9YNOYB2oo/W2YcJ/ZjV5TFjs1xYGfyAW0VhdqD+S7vwqAPmcBKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tIoA4mmbsaO8N0Xui0453uchWyNaRKHK3TVYcEnAfDx51TuTJiIphjiGtOA8VbPTMFQee5YHumVuYdVem19oiEwnudAj+EnGzAYAgULqJKcLNslmFctwnVEIBebBKX3sKRwHxKEf2dNYwFaLGffp0dro8vlH5/34AXhtkG14noBQRTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", + "hash": "3521012537060428516113870265561264404353424949985428093281747073703754036558" + } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 5d2c6d71e9..c23433b6dc 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,7 +3,10 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; -import { ecdsaProgram } from '../../src/examples/crypto/ecdsa/ecdsa.js'; +import { + ecdsa, + keccakAndEcdsa, +} from '../../src/examples/crypto/ecdsa/ecdsa.js'; import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions @@ -39,7 +42,8 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ createDex().Dex, GroupCS, BitwiseCS, - ecdsaProgram, + ecdsa, + keccakAndEcdsa, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; From 0cdf3d97b186b2829d263bdadbf0b77653a44b6a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 12 Dec 2023 18:03:09 +0100 Subject: [PATCH 1090/1215] remove useless low-level module --- src/lib/keccak.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index da64a42694..70d6416355 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -19,35 +19,6 @@ const Keccak = { preNist(len: 256 | 384 | 512, message: Field[]): Field[] { return preNist(len, message); }, - - /** - * Low-level API to access Keccak operating on 64-bit words. - * - * @example - * ```ts - * const message = [0x02, 0x03, 0x04, 0x05].map(Field.from); - * const padded = Keccak.LowLevel.padMessage(message, 256, false); - * const hash = Keccak.LowLevel.hash(padded, 256); - * // hash is an array of 4 Field elements of 64 bits each - * ``` - */ - LowLevel: { - hash(paddedMessage: Field[], length: 256 | 384 | 512): Field[] { - length /= 64; - let capacity = length * 2; - let rate = 25 - capacity; - return sponge(paddedMessage, length, capacity, rate); - }, - - padMessage( - message: Field[], - length: 256 | 384 | 512, - isNist: boolean - ): Field[] { - let paddedBytes = pad(message, 200 - length / 4, isNist); - return bytesToWords(paddedBytes); - }, - }, }; // KECCAK CONSTANTS From 0f17c6a0ff53821a60162401782eaa1676fa9926 Mon Sep 17 00:00:00 2001 From: Todd Chapman <6946868+TtheBC01@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:45:34 -0800 Subject: [PATCH 1091/1215] Update README-dev.md added small helper hint to README-dev.md that could save others some time --- README-dev.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README-dev.md b/README-dev.md index 6c37bfefd2..7bb5623c74 100644 --- a/README-dev.md +++ b/README-dev.md @@ -2,6 +2,14 @@ This README includes information that is helpful for o1js core contributors. +## Setting up the repo on your local + +```sh +git clone https://github.com/o1-labs/o1js.git +cd o1js +git submodule update --init --recursive +``` + ## Run examples using Node.js ```sh From 169a4f8468bad31f31e4bd657292cb005cf8f255 Mon Sep 17 00:00:00 2001 From: Todd Chapman <6946868+TtheBC01@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:46:24 -0800 Subject: [PATCH 1092/1215] Update README-dev.md text --- README-dev.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README-dev.md b/README-dev.md index 7bb5623c74..651359df05 100644 --- a/README-dev.md +++ b/README-dev.md @@ -4,6 +4,8 @@ This README includes information that is helpful for o1js core contributors. ## Setting up the repo on your local +After cloning the repo, you must fetch external submodules for the following examples to work. + ```sh git clone https://github.com/o1-labs/o1js.git cd o1js From 5b185aab9f3db5a2c714524f76ff2f80e8f6b8e6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 08:18:20 +0100 Subject: [PATCH 1093/1215] avoid a few constraints for xoring with 0 --- src/lib/gadgets/bitwise.ts | 11 ++------ src/lib/keccak.ts | 52 ++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 027d996488..4c930de649 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -61,15 +61,8 @@ function xor(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert( - a.toBigInt() < max, - `${a.toBigInt()} does not fit into ${padLength} bits` - ); - - assert( - b.toBigInt() < max, - `${b.toBigInt()} does not fit into ${padLength} bits` - ); + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); return new Field(a.toBigInt() ^ b.toBigInt()); } diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 70d6416355..9d99e9fd4f 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -132,22 +132,19 @@ const theta = (state: Field[][]): Field[][] => { // XOR the elements of each row together // for all i in {0..4}: C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] - const stateC = stateA.map((row) => - row.reduce((acc, value) => Gadgets.xor(acc, value, KECCAK_WORD)) - ); + const stateC = stateA.map((row) => row.reduce(xor)); // for all i in {0..4}: D[i] = C[i-1] xor ROT(C[i+1], 1) - const stateD = Array.from({ length: KECCAK_DIM }, (_, x) => - Gadgets.xor( - stateC[(x + KECCAK_DIM - 1) % KECCAK_DIM], - Gadgets.rotate(stateC[(x + 1) % KECCAK_DIM], 1, 'left'), - KECCAK_WORD + const stateD = Array.from({ length: KECCAK_DIM }, (_, i) => + xor( + stateC[(i + KECCAK_DIM - 1) % KECCAK_DIM], + Gadgets.rotate(stateC[(i + 1) % KECCAK_DIM], 1, 'left') ) ); // for all i in {0..4} and j in {0..4}: E[i,j] = A[i,j] xor D[i] const stateE = stateA.map((row, index) => - row.map((elem) => Gadgets.xor(elem, stateD[index], KECCAK_WORD)) + row.map((elem) => xor(elem, stateD[index])) ); return stateE; @@ -182,7 +179,7 @@ function piRho(state: Field[][]): Field[][] { const stateE = state; const stateB = State.zeros(); - // for all i in {0..4} and j in {0..4}: B[y,2x+3y] = ROT(E[i,j], r[i,j]) + // for all i in {0..4} and j in {0..4}: B[j,2i+3j] = ROT(E[i,j], r[i,j]) for (let i = 0; i < KECCAK_DIM; i++) { for (let j = 0; j < KECCAK_DIM; j++) { stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate( @@ -211,15 +208,14 @@ function chi(state: Field[][]): Field[][] { // for all i in {0..4} and j in {0..4}: F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) for (let i = 0; i < KECCAK_DIM; i++) { for (let j = 0; j < KECCAK_DIM; j++) { - stateF[i][j] = Gadgets.xor( + stateF[i][j] = xor( stateB[i][j], Gadgets.and( // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 Gadgets.not(stateB[(i + 1) % KECCAK_DIM][j], KECCAK_WORD, false), stateB[(i + 2) % KECCAK_DIM][j], KECCAK_WORD - ), - KECCAK_WORD + ) ); } } @@ -232,7 +228,7 @@ function chi(state: Field[][]): Field[][] { function iota(state: Field[][], rc: bigint): Field[][] { const stateG = state; - stateG[0][0] = Gadgets.xor(stateG[0][0], Field.from(rc), KECCAK_WORD); + stateG[0][0] = xor(stateG[0][0], Field.from(rc)); return stateG; } @@ -249,8 +245,8 @@ function round(state: Field[][], rc: bigint): Field[][] { } // Keccak permutation function with a constant number of rounds -function permutation(state: Field[][], rc: bigint[]): Field[][] { - return rc.reduce((acc, value) => round(acc, value), state); +function permutation(state: Field[][], rcs: bigint[]): Field[][] { + return rcs.reduce((state, rc) => round(state, rc), state); } // KECCAK SPONGE @@ -273,12 +269,11 @@ function absorb( let state = State.zeros(); - // array of capacity zero bytes + // array of capacity zero words const zeros = Array(capacity).fill(Field.from(0)); for (let idx = 0; idx < paddedMessage.length; idx += rate) { - // split into blocks of rate bits - // for each block of rate bits in the padded message -> this is rate bytes + // split into blocks of rate words const block = paddedMessage.slice(idx, idx + rate); // pad the block with 0s to up to KECCAK_STATE_LENGTH_WORDS words const paddedBlock = block.concat(zeros); @@ -298,10 +293,8 @@ function squeeze(state: State, length: number, rate: number): Field[] { const squeezes = Math.floor(length / rate) + 1; assert(squeezes === 1, 'squeezes should be 1'); - // first state to be squeezed - const words = State.toWords(state); - // Obtain the hash selecting the first `length` words of the output array + const words = State.toWords(state); const hashed = words.slice(0, length); return hashed; } @@ -321,7 +314,6 @@ function sponge( // squeeze const hashed = squeeze(state, length, rate); - return hashed; } @@ -430,10 +422,8 @@ const State = { `invalid \`b\` dimensions (should be ${KECCAK_DIM})` ); - // Calls Gadgets.xor on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix - return a.map((row, i) => - row.map((value, j) => Gadgets.xor(value, b[i][j], 64)) - ); + // Calls xor() on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix + return a.map((row, i) => row.map((x, j) => xor(x, b[i][j]))); }, }; @@ -479,3 +469,11 @@ function chunk(array: T[], size: number): T[][] { array.slice(size * i, size * (i + 1)) ); } + +// xor which avoids doing anything on 0 inputs +// (but doesn't range-check the other input in that case) +function xor(x: Field, y: Field): Field { + if (x.isConstant() && x.toBigInt() === 0n) return y; + if (y.isConstant() && y.toBigInt() === 0n) return x; + return Gadgets.xor(x, y, 64); +} From 60783a5ee8d33a556c124253132ed61e484cab12 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 08:47:18 +0100 Subject: [PATCH 1094/1215] vk regression --- tests/vk-regression/vk-regression.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index bd1167cb56..657960f923 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -216,20 +216,20 @@ } }, "ecdsa": { - "digest": "2c5e8b21a7b92454614abcf51fb039271f617767d53c11a28e0967ff8f7eda4b", + "digest": "baf90de64c1b6b5b5938a0b80a8fdb70bdbbc84a292dd7eccfdbce470c10b4c", "methods": { "sha3": { - "rows": 14660, - "digest": "80edbd7c3b18635749d8ea72c7c3fa25" + "rows": 14494, + "digest": "949539824d56622702d9ac048e8111e9" }, "verifyEcdsa": { - "rows": 53552, - "digest": "19e415d0e19f292917fa6aa81133ac87" + "rows": 53386, + "digest": "5a234cff8ea48ce653cbd7efa2e1c241" } }, "verificationKey": { - "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAGGpFLTVX/ZRNJvF4z8+jcQhWnwFvV1AulW8zoTvLrYluoItNz+OoFFO8QH5STv4bv1eLYKwvb8kzQKsRREZEBnH+2W6nrDuUAbAB4SoKC1kPYvdJAEGC5FgXIA9YNOYB2oo/W2YcJ/ZjV5TFjs1xYGfyAW0VhdqD+S7vwqAPmcBKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tIoA4mmbsaO8N0Xui0453uchWyNaRKHK3TVYcEnAfDx51TuTJiIphjiGtOA8VbPTMFQee5YHumVuYdVem19oiEwnudAj+EnGzAYAgULqJKcLNslmFctwnVEIBebBKX3sKRwHxKEf2dNYwFaLGffp0dro8vlH5/34AXhtkG14noBQRTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", - "hash": "3521012537060428516113870265561264404353424949985428093281747073703754036558" + "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAA+OYudpfMnM3umoBqQoxTWfNZd6qs+yEusT1/Zn8mwWpEpLlhrEUyhWP5AW6XrVXTRFDrBUzfMPXR4WK6zFDRbXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tRp5+4JWVGK/zZHITJMRdMkalsW2dOqrVH7PSkjn6cwm4rs6BCduLjQE4r1GKVQsyhqr2DxOV+IX3iO8XzVueFJfH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", + "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" } } } \ No newline at end of file From 08448a8261797b98e3b0a67ac7d5a75c4b83f9b5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 09:05:15 +0100 Subject: [PATCH 1095/1215] missing from merge --- src/lib/keccak-old.unit-test.ts | 278 ++++++++++++++++++ .../vk-regression/plain-constraint-system.ts | 48 ++- 2 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 src/lib/keccak-old.unit-test.ts diff --git a/src/lib/keccak-old.unit-test.ts b/src/lib/keccak-old.unit-test.ts new file mode 100644 index 0000000000..c934bcf15e --- /dev/null +++ b/src/lib/keccak-old.unit-test.ts @@ -0,0 +1,278 @@ +import { test, Random } from './testing/property.js'; +import { UInt8 } from './int.js'; +import { Hash } from './hash.js'; +import { Provable } from './provable.js'; +import { expect } from 'expect'; +import assert from 'assert'; + +let RandomUInt8 = Random.map(Random.uint8, (x) => UInt8.from(x)); + +// Test constructor +test(Random.uint8, Random.uint8, (x, y, assert) => { + let z = new UInt8(x); + assert(z instanceof UInt8); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + assert(z.isConstant()); + + assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value)) instanceof UInt8 && z.toBigInt() === x); + + z = new UInt8(y); + assert(z instanceof UInt8); + assert(z.toString() === y.toString()); + assert(z.toJSON() === y.toString()); +}); + +// handles all numbers up to 2^8 +test(Random.nat(255), (n, assert) => { + assert(UInt8.from(n).toString() === String(n)); +}); + +// throws on negative numbers +test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); + +// throws on numbers >= 2^8 +test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); + +runHashFunctionTests(); +console.log('OCaml tests pass! 🎉'); + +// test digest->hex and hex->digest conversions +checkHashInCircuit(); +console.log('hashing digest conversions matches! 🎉'); + +// check in-circuit +function checkHashInCircuit() { + Provable.runAndCheck(() => { + let data = Random.array(RandomUInt8, Random.nat(32)) + .create()() + .map((x) => Provable.witness(UInt8, () => UInt8.from(x))); + + checkHashConversions(data); + }); +} + +function checkHashConversions(data: UInt8[]) { + Provable.asProver(() => { + expectDigestToEqualHex(Hash.SHA224.hash(data)); + expectDigestToEqualHex(Hash.SHA256.hash(data)); + expectDigestToEqualHex(Hash.SHA384.hash(data)); + expectDigestToEqualHex(Hash.SHA512.hash(data)); + expectDigestToEqualHex(Hash.Keccak256.hash(data)); + }); +} + +function expectDigestToEqualHex(digest: UInt8[]) { + const hex = UInt8.toHex(digest); + expect(equals(digest, UInt8.fromHex(hex))).toBe(true); +} + +function equals(a: UInt8[], b: UInt8[]): boolean { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); + + return true; +} + +/** + * Based off the following unit tests from the OCaml implementation: + * https://github.com/MinaProtocol/mina/blob/69d6ea4a3b7ca1690cf8f41d4598cb7484359e1d/src/lib/crypto/kimchi_backend/gadgets/keccak.ml#L646 + */ +function runHashFunctionTests() { + // Positive Tests + testExpected({ + nist: false, + length: 256, + message: '30', + expected: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', + }); + + testExpected({ + nist: true, + length: 512, + message: '30', + expected: + '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', + }); + + testExpected({ + nist: false, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }); + + testExpected({ + nist: false, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', + }); + + testExpected({ + nist: true, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', + }); + + testExpected({ + nist: false, + length: 256, + message: + '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', + }); + + testExpected({ + nist: false, + length: 256, + message: + 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', + }); + + testExpected({ + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }); + + testExpected({ + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }); + + testExpected({ + nist: false, + length: 256, + message: 'a2c0', + expected: + '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', + }); + + testExpected({ + nist: false, + length: 256, + message: '0a2c', + expected: + '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', + }); + + testExpected({ + nist: false, + length: 256, + message: '00', + expected: + 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', + }); + + // Negative tests + try { + testExpected({ + nist: false, + length: 256, + message: 'a2c', + expected: + '07f02d241eeba9c909a1be75e08d9e8ac3e61d9e24fa452a6785083e1527c467', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: '0', + expected: + 'f39f4526920bb4c096e5722d64161ea0eb6dbd0b4ff0d812f31d56fb96142084', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: '30', + expected: + 'f9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e4', + }); + assert(false, 'Expected to throw'); + } catch (e) {} + + try { + testExpected({ + nist: true, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }); + assert(false, 'Expected to throw'); + } catch (e) {} +} + +function testExpected({ + message, + expected, + nist = false, + length = 256, +}: { + message: string; + expected: string; + nist: boolean; + length: number; +}) { + Provable.runAndCheck(() => { + assert(message.length % 2 === 0); + + let fields = UInt8.fromHex(message); + let expectedHash = UInt8.fromHex(expected); + + Provable.asProver(() => { + if (nist) { + let hashed; + switch (length) { + case 224: + hashed = Hash.SHA224.hash(fields); + break; + case 256: + hashed = Hash.SHA256.hash(fields); + break; + case 384: + hashed = Hash.SHA384.hash(fields); + break; + case 512: + hashed = Hash.SHA512.hash(fields); + break; + default: + assert(false); + } + equals(hashed!, expectedHash); + } else { + let hashed = Hash.Keccak256.hash(fields); + equals(hashed, expectedHash); + } + }); + }); +} diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index b5d0c1f447..20bf05fa12 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -1,6 +1,6 @@ -import { Field, Group, Gadgets, Provable, Scalar } from 'o1js'; +import { Field, Group, Gadgets, Provable, Scalar, Hash, UInt8 } from 'o1js'; -export { GroupCS, BitwiseCS }; +export { GroupCS, BitwiseCS, HashCS }; const GroupCS = constraintSystem('Group Primitive', { add() { @@ -84,6 +84,50 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { }, }); +const HashCS = constraintSystem('Hashes', { + SHA224() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA224.hash(xs); + }, + + SHA256() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA256.hash(xs); + }, + + SHA384() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA384.hash(xs); + }, + + SHA512() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.SHA512.hash(xs); + }, + + Keccak256() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(UInt8, () => UInt8.from(x)) + ); + Hash.Keccak256.hash(xs); + }, + + Poseidon() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(Field, () => Field(x)) + ); + Hash.Poseidon.hash(xs); + }, +}); + // mock ZkProgram API for testing function constraintSystem( From 8ff8380b9ceca1b8433802659002d9df1bdc52be Mon Sep 17 00:00:00 2001 From: Ursulafe <152976968+Ursulafe@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:40:35 +0000 Subject: [PATCH 1096/1215] typo fix --- src/examples/internals/advanced-provable-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/internals/advanced-provable-types.ts b/src/examples/internals/advanced-provable-types.ts index 6f7af3a68a..dc3b95b530 100644 --- a/src/examples/internals/advanced-provable-types.ts +++ b/src/examples/internals/advanced-provable-types.ts @@ -58,7 +58,7 @@ expect(accountUpdateRecovered.lazyAuthorization).not.toEqual( /** * Provable.runAndCheck() can be used to run a circuit in "prover mode". * That means - * -) witness() and asProver() blocks are excuted + * -) witness() and asProver() blocks are executed * -) constraints are checked; failing assertions throw an error */ Provable.runAndCheck(() => { From adf7d22cf170ded1c951ce43124d006079b1aa87 Mon Sep 17 00:00:00 2001 From: Ursulafe <152976968+Ursulafe@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:40:48 +0000 Subject: [PATCH 1097/1215] typo fix --- src/examples/zkapps/dex/erc20.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index 3d0b3a4a25..b18ba43dea 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -193,7 +193,7 @@ class TrivialCoin extends SmartContract implements Erc20 { zkapp.requireSignature(); } - // for letting a zkapp do whatever it wants, as long as no tokens are transfered + // for letting a zkapp do whatever it wants, as long as no tokens are transferred // TODO: atm, we have to restrict the zkapp to have no children // -> need to be able to witness a general layout of account updates @method approveZkapp(callback: Experimental.Callback) { From bed1b498a5c373d40f86abb3081ba173b62385b0 Mon Sep 17 00:00:00 2001 From: Ursulafe <152976968+Ursulafe@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:41:49 +0000 Subject: [PATCH 1098/1215] typo fix --- src/examples/zkapps/voting/preconditions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/voting/preconditions.ts b/src/examples/zkapps/voting/preconditions.ts index 946ae73d57..c8dccfab59 100644 --- a/src/examples/zkapps/voting/preconditions.ts +++ b/src/examples/zkapps/voting/preconditions.ts @@ -17,7 +17,7 @@ export class ElectionPreconditions { export class ParticipantPreconditions { minMina: UInt64; - maxMina: UInt64; // have to make this "generic" so it applys for both candidate and voter instances + maxMina: UInt64; // have to make this "generic" so it applies for both candidate and voter instances static get default(): ParticipantPreconditions { return new ParticipantPreconditions(UInt64.zero, UInt64.MAXINT()); From 763d38f2886cefd156173141d7c17fa40d9c1a48 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 11:10:06 +0100 Subject: [PATCH 1099/1215] remove some unnecessary changes --- src/examples/zkapps/hashing/hash.ts | 36 +++-------------------------- src/index.ts | 2 +- src/provable/field-bigint.ts | 4 +--- src/snarky.d.ts | 13 +---------- 4 files changed, 6 insertions(+), 49 deletions(-) diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts index 308cf48bce..e593afae35 100644 --- a/src/examples/zkapps/hashing/hash.ts +++ b/src/examples/zkapps/hashing/hash.ts @@ -8,44 +8,14 @@ import { method, Permissions, Struct, -} from 'snarkyjs'; + Provable, +} from 'o1js'; let initialCommitment: Field = Field(0); // 32 UInts export class HashInput extends Struct({ - data: [ - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - UInt8, - ], + data: Provable.Array(UInt8, 32), }) {} export class HashStorage extends SmartContract { diff --git a/src/index.ts b/src/index.ts index 9a5868e59c..bbd5bbc558 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ export { } from './lib/circuit_value.js'; export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; -export { UInt8, UInt32, UInt64, Int64, Sign } from './lib/int.js'; +export { UInt32, UInt64, Int64, Sign, UInt8 } from './lib/int.js'; export { Gadgets } from './lib/gadgets/gadgets.js'; export { Types } from './bindings/mina-transaction/types.js'; diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index 06ff9fa327..c23e0e0bc4 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -6,12 +6,11 @@ import { ProvableBigint, } from '../bindings/lib/provable-bigint.js'; -export { Field, Bool, UInt8, UInt32, UInt64, Sign }; +export { Field, Bool, UInt32, UInt64, Sign }; export { pseudoClass, sizeInBits, checkRange, checkField }; type Field = bigint; type Bool = 0n | 1n; -type UInt8 = bigint; type UInt32 = bigint; type UInt64 = bigint; @@ -98,7 +97,6 @@ function Unsigned(bits: number) { } ); } -const UInt8 = Unsigned(8); const UInt32 = Unsigned(32); const UInt64 = Unsigned(64); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 2a496454a7..871fbeecaf 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -525,18 +525,7 @@ declare const Snarky: { }; }; - sha: { - create( - message: MlArray, - nist: boolean, - length: number - ): MlArray; - - fieldBytesFromHex(hex: string): MlArray; - - checkBits(value: FieldVar, bits: number): void; - }; - + // TODO: implement in TS poseidon: { update( state: MlArray, From 6fd370dedc7d0bdbc75ec07deb900347daeab3bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 11:48:36 +0100 Subject: [PATCH 1100/1215] work on uint8 and make it compile --- src/lib/int.ts | 189 ++++++++++++++--------------------------- src/lib/keccak.ts | 8 +- src/lib/util/arrays.ts | 14 +++ 3 files changed, 81 insertions(+), 130 deletions(-) create mode 100644 src/lib/util/arrays.ts diff --git a/src/lib/int.ts b/src/lib/int.ts index 6b9d46b7d4..d4947493ed 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -3,7 +3,9 @@ import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; -import { Snarky } from '../snarky.js'; +import { Gadgets } from './gadgets/gadgets.js'; +import { withMessage } from './field.js'; +import { chunkString } from './util/arrays.js'; // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -976,28 +978,12 @@ class UInt8 extends Struct({ * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a {@link UInt8}. * The max value of a {@link UInt8} is `2^8 - 1 = 255`. * - * * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. */ constructor(x: number | bigint | string | Field | UInt8) { - if (x instanceof UInt8) return x; - + if (x instanceof UInt8) x = x.value; super({ value: Field(x) }); - this.check(); - } - - /** - * Static method to create a {@link UInt8} with value `0`. - */ - static get zero() { - return UInt8.from(0); - } - - /** - * Static method to create a {@link UInt8} with value `1`. - */ - static get one() { - return UInt8.from(1); + UInt8.checkConstant(this.value); } /** @@ -1158,27 +1144,15 @@ class UInt8 extends Struct({ * The method will throw if one of the inputs exceeds 8 bits. * * - * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare with this {@link UInt8}. * * @return A {@link Bool} representing if this {@link UInt8} is less than or equal another {@link UInt8} value. */ - lessThanOrEqual(y: UInt8) { + lessThanOrEqual(y: UInt8): Bool { if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - // TODO: Enable when rangeCheck works in proofs - // let xMinusY = this.value.sub(y.value).seal(); - // UInt8.#rangeCheck(xMinusY); - - // let yMinusX = xMinusY.neg(); - // UInt8.#rangeCheck(yMinusX); - - // x <= y if y - x fits in 64 bits - // return yMinusX; - - // TODO: Remove this when rangeCheck works in proofs - return this.value.lessThanOrEqual(y.value); } + throw Error('Not implemented'); } /** @@ -1193,14 +1167,15 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare with this {@link UInt8}. * * @return A {@link Bool} representing if this {@link UInt8} is less than another {@link UInt8} value. */ - lessThan(value: UInt8) { - return this.lessThanOrEqual(value).and( - this.value.equals(value.value).not() - ); + lessThan(y: UInt8): Bool { + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() < y.value.toBigInt()); + } + throw Error('Not implemented'); } /** @@ -1213,11 +1188,22 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThan(value: UInt8, message?: string) { - this.lessThan(value).assertEquals(true, message); + assertLessThan(y: UInt8, 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(`UInt8.assertLessThan: expected ${x0} < ${y0}`); + } + return; + } + // x < y <=> x + 1 <= y + let xPlus1 = new UInt8(this.value.add(1)); + xPlus1.assertLessThanOrEqual(y, message); } /** @@ -1230,25 +1216,26 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThanOrEqual(value: UInt8, message?: string) { - if (this.value.isConstant() && value.value.isConstant()) { + assertLessThanOrEqual(y: UInt8, message?: string) { + if (this.value.isConstant() && y.value.isConstant()) { let x0 = this.value.toBigInt(); - let y0 = value.value.toBigInt(); + let y0 = y.value.toBigInt(); if (x0 > y0) { if (message !== undefined) throw Error(message); throw Error(`UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}`); } return; } - // TODO: Enable when rangeCheck works in proofs - // let yMinusX = value.value.sub(this.value).seal(); - // UInt8.#rangeCheck(yMinusX); - - // TODO: Remove this when rangeCheck works in proofs - return this.lessThanOrEqual(value).assertEquals(true, message); + try { + // x <= y <=> y - x >= 0 <=> y - x in [0, 2^8) + let yMinusX = y.value.sub(this.value).seal(); + Gadgets.rangeCheck8(yMinusX); + } catch (err) { + throw withMessage(err, message); + } } /** @@ -1263,12 +1250,12 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods currently only support Field elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare with this {@link UInt8}. * * @return A {@link Bool} representing if this {@link UInt8} is greater than another {@link UInt8} value. */ - greaterThan(value: UInt8) { - return value.lessThan(this); + greaterThan(y: UInt8) { + return y.lessThan(this); } /** @@ -1283,12 +1270,12 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare with this {@link Field}. + * @param y - the {@link UInt8} value to compare with this {@link Field}. * * @return A {@link Bool} representing if this {@link UInt8} is greater than or equal another {@link UInt8} value. */ - greaterThanOrEqual(value: UInt8) { - return this.lessThan(value).not(); + greaterThanOrEqual(y: UInt8) { + return this.lessThan(y).not(); } /** @@ -1301,11 +1288,11 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertGreaterThan(value: UInt8, message?: string) { - value.assertLessThan(this, message); + assertGreaterThan(y: UInt8, message?: string) { + y.assertLessThan(this, message); } /** @@ -1318,11 +1305,11 @@ class UInt8 extends Struct({ * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. * The method will throw if one of the inputs exceeds 8 bits. * - * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertGreaterThanOrEqual(value: UInt8, message?: string) { - value.assertLessThanOrEqual(this, message); + assertGreaterThanOrEqual(y: UInt8, message?: string) { + y.assertLessThanOrEqual(this, message); } /** @@ -1330,11 +1317,11 @@ class UInt8 extends Struct({ * * **Important**: If an assertion fails, the code throws an error. * - * @param value - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertEquals(value: number | bigint | UInt8, message?: string) { - let y_ = new UInt8(value); + assertEquals(y: number | bigint | UInt8, message?: string) { + let y_ = new UInt8(y); this.toField().assertEquals(y_.toField(), message); } @@ -1395,23 +1382,8 @@ class UInt8 extends Struct({ * * @param value - the {@link UInt8} element to check. */ - check() { - // TODO: Enable when rangeCheck works in proofs - // UInt8.#rangeCheck(this.value); - this.value.toBits(UInt8.NUM_BITS); - } - - /** - * Implementation of {@link Provable.fromFields} for the {@link UInt8} type. - * - * **Warning**: This function is designed for internal use. It is not intended to be used by a zkApp developer. - * - * @param fields - an array of {@link UInt8} serialized from {@link Field} elements. - * - * @return An array of {@link UInt8} elements of the given array. - */ - fromFields(xs: Field[]): UInt8[] { - return xs.map((x) => new UInt8(x)); + static check(x: { value: Field }) { + Gadgets.rangeCheck8(x.value); } /** @@ -1443,9 +1415,7 @@ class UInt8 extends Struct({ * @return A {@link UInt32} equivalent to the {@link UInt8}. */ toUInt32(): UInt32 { - let uint32 = new UInt32(this.value); - UInt32.check(uint32); - return uint32; + return new UInt32(this.value); } /** @@ -1460,9 +1430,7 @@ class UInt8 extends Struct({ * @return A {@link UInt64} equivalent to the {@link UInt8}. * */ toUInt64(): UInt64 { - let uint64 = new UInt64(this.value); - UInt64.check(uint64); - return uint64; + return new UInt64(this.value); } /** @@ -1487,59 +1455,34 @@ class UInt8 extends Struct({ return this.value.isConstant(); } + // TODO: these might be better on a separate `Bytes` class static fromHex(xs: string): UInt8[] { - return Snarky.sha - .fieldBytesFromHex(xs) - .map((x) => UInt8.from(Field(x))) - .slice(1); + let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); + return bytes.map(UInt8.from); } - static toHex(xs: UInt8[]): string { - return xs - .map((x) => x.value) - .map((f) => Field.toBytes(f)[0].toString(16).padStart(2, '0')) - .join(''); + return xs.map((x) => x.toBigInt().toString(16).padStart(2, '0')).join(''); } /** * Creates a {@link UInt8} with a value of 255. */ static MAXINT() { - return new UInt8(Field((1n << BigInt(this.NUM_BITS)) - 1n)); + return new UInt8(Field((1n << BigInt(UInt8.NUM_BITS)) - 1n)); } /** * Creates a new {@link UInt8}. */ - static from( - x: UInt8 | UInt64 | UInt32 | Field | number | string | bigint | number[] - ) { + static from(x: UInt8 | UInt64 | UInt32 | Field | number | bigint) { if (x instanceof UInt64 || x instanceof UInt32 || x instanceof UInt8) x = x.value; - - if (Array.isArray(x)) { - return new this(Field.fromBytes(x)); - } - - return new this(this.checkConstant(Field(x))); + return new UInt8(UInt8.checkConstant(Field(x))); } private static checkConstant(x: Field) { if (!x.isConstant()) return x; - x.toBits(UInt8.NUM_BITS); + Gadgets.rangeCheck8(x); return x; } - - // TODO: rangeCheck does not prove as of yet, waiting on https://github.com/MinaProtocol/mina/pull/12524 to merge. - static #rangeCheck(x: UInt8 | Field) { - if (isUInt8(x)) x = x.value; - if (x.isConstant()) this.checkConstant(x); - - // Throws an error if the value is not in the range [0, 2^UInt8.NUM_BITS - 1] - Snarky.sha.checkBits(x.value, UInt8.NUM_BITS); - } -} - -function isUInt8(x: unknown): x is UInt8 { - return x instanceof UInt8; } diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 9d99e9fd4f..a1e4bb8728 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -3,6 +3,7 @@ import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; import { rangeCheck8 } from './gadgets/range-check.js'; import { Provable } from './provable.js'; +import { chunk } from './util/arrays.js'; export { Keccak }; @@ -463,13 +464,6 @@ function wordsToBytes(words: Field[]): Field[] { return words.flatMap(wordToBytes); } -function chunk(array: T[], size: number): T[][] { - assert(array.length % size === 0, 'invalid input length'); - return Array.from({ length: array.length / size }, (_, i) => - array.slice(size * i, size * (i + 1)) - ); -} - // xor which avoids doing anything on 0 inputs // (but doesn't range-check the other input in that case) function xor(x: Field, y: Field): Field { diff --git a/src/lib/util/arrays.ts b/src/lib/util/arrays.ts new file mode 100644 index 0000000000..2a1a913eef --- /dev/null +++ b/src/lib/util/arrays.ts @@ -0,0 +1,14 @@ +import { assert } from '../gadgets/common.js'; + +export { chunk, chunkString }; + +function chunk(array: T[], size: number): T[][] { + assert(array.length % size === 0, 'invalid input length'); + return Array.from({ length: array.length / size }, (_, i) => + array.slice(size * i, size * (i + 1)) + ); +} + +function chunkString(str: string, size: number): string[] { + return chunk([...str], size).map((c) => c.join('')); +} From 900a3449bd9379a693de073f72dcfe8f85c81d4a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 12:02:25 +0100 Subject: [PATCH 1101/1215] export Hash, rename old Hash to HashHelpers --- src/bindings | 2 +- src/index.ts | 2 +- src/lib/hash.ts | 28 +++------------------------- src/provable/poseidon-bigint.ts | 6 +++--- 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/bindings b/src/bindings index c09afcd2fc..7946599c5f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c09afcd2fc902abb3db7c029740d401414f76f8b +Subproject commit 7946599c5f1636576519601dbd2c20aecc90a502 diff --git a/src/index.ts b/src/index.ts index bbd5bbc558..4b896dd901 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ export { } from './lib/foreign-field.js'; export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; -export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Poseidon, TokenSymbol, Hash } from './lib/hash.js'; export { Keccak } from './lib/keccak.js'; export * from './lib/signature.js'; diff --git a/src/lib/hash.ts b/src/lib/hash.ts index bf14707f7c..d986d7ea47 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -4,10 +4,9 @@ import { Field } from './core.js'; import { createHashHelpers } from './hash-generic.js'; import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; -import { UInt8 } from './int.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; -import { MlArray } from './ml/base.js'; +import { Keccak } from './keccak.js'; // external API export { Poseidon, TokenSymbol, Hash }; @@ -208,29 +207,8 @@ function toBigints(fields: Field[]) { return fields.map((x) => x.toBigInt()); } -function buildSHA(length: 224 | 256 | 384 | 512, nist: boolean) { - return { - hash(message: UInt8[]): UInt8[] { - return Snarky.sha - .create(MlArray.to(message.map((f) => f.toField().value)), nist, length) - .map((f) => UInt8.from(Field(f))) - .slice(1); - }, - }; -} - const Hash = { hash: Poseidon.hash, - - Poseidon: Poseidon, - - SHA224: buildSHA(224, true), - - SHA256: buildSHA(256, true), - - SHA384: buildSHA(384, true), - - SHA512: buildSHA(512, true), - - Keccak256: buildSHA(256, false), + Poseidon, + Keccak, }; diff --git a/src/provable/poseidon-bigint.ts b/src/provable/poseidon-bigint.ts index 2ef684d585..906ab2a2d5 100644 --- a/src/provable/poseidon-bigint.ts +++ b/src/provable/poseidon-bigint.ts @@ -7,7 +7,7 @@ import { createHashHelpers } from '../lib/hash-generic.js'; export { Poseidon, - Hash, + HashHelpers, HashInput, prefixes, packToFields, @@ -20,8 +20,8 @@ export { type HashInput = GenericHashInput; const HashInput = createHashInput(); -const Hash = createHashHelpers(Field, Poseidon); -let { hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { hashWithPrefix } = HashHelpers; const HashLegacy = createHashHelpers(Field, PoseidonLegacy); From 3bbd4544dfa731423bb9da6001e11e9d166805fa Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 12:18:57 +0100 Subject: [PATCH 1102/1215] add rangeCheck16 --- src/lib/gadgets/gadgets.ts | 10 ++++++++++ src/lib/gadgets/range-check.ts | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 038646d03d..27cae55bf7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -4,6 +4,7 @@ import { compactMultiRangeCheck, multiRangeCheck, + rangeCheck16, rangeCheck64, rangeCheck8, } from './range-check.js'; @@ -41,6 +42,15 @@ const Gadgets = { return rangeCheck64(x); }, + /** + * Asserts that the input value is in the range [0, 2^16). + * + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. + */ + rangeCheck16(x: Field) { + return rangeCheck16(x); + }, + /** * Asserts that the input value is in the range [0, 2^8). * diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 147ef1a6a4..d53d8f5e51 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -2,7 +2,13 @@ import { Field } from '../field.js'; import { Gates } from '../gates.js'; import { assert, bitSlice, exists, toVar, toVars } from './common.js'; -export { rangeCheck64, rangeCheck8, multiRangeCheck, compactMultiRangeCheck }; +export { + rangeCheck64, + rangeCheck8, + rangeCheck16, + multiRangeCheck, + compactMultiRangeCheck, +}; export { l, l2, l3, lMask, l2Mask }; /** @@ -208,6 +214,18 @@ function rangeCheck1Helper(inputs: { ); } +function rangeCheck16(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 16n, + `rangeCheck16: expected field to fit in 8 bits, got ${x}` + ); + return; + } + // check that x fits in 16 bits + x.rangeCheckHelper(16).assertEquals(x); +} + function rangeCheck8(x: Field) { if (x.isConstant()) { assert( From 0e0480727a0ee0d5c824d49465c9f1348648d6d5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 12:19:14 +0100 Subject: [PATCH 1103/1215] fix divMod --- src/lib/int.ts | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index d4947493ed..c5787c3143 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1090,45 +1090,36 @@ class UInt8 extends Struct({ /** * Get the quotient and remainder of a {@link UInt8} value divided by another {@link UInt8} element. * - * @param value - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. + * @param y - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. * * @return The quotient and remainder of the two values. */ - divMod(value: UInt8 | number) { + divMod(y: UInt8 | bigint | number) { let x = this.value; - let y_ = UInt8.from(value).value; + let y_ = UInt8.from(y).value.seal(); if (this.value.isConstant() && y_.isConstant()) { let xn = x.toBigInt(); let yn = y_.toBigInt(); let q = xn / yn; let r = xn - q * yn; - return { - quotient: UInt8.from(Field(q)), - rest: UInt8.from(Field(r)), - }; + return { quotient: UInt8.from(q), rest: UInt8.from(r) }; } - y_ = y_.seal(); - let q = Provable.witness( - Field, - () => new Field(x.toBigInt() / y_.toBigInt()) - ); - - // TODO: Enable when rangeCheck works in proofs - // UInt8.#rangeCheck(q); - - // TODO: Could be a bit more efficient + // prove that x === q * y + r, where 0 <= r < y + let q = Provable.witness(Field, () => Field(x.toBigInt() / y_.toBigInt())); let r = x.sub(q.mul(y_)).seal(); - // TODO: Enable when rangeCheck works in proofs - // UInt8.#rangeCheck(r); + // q, r being 16 bits is enough for them to be 8 bits, + // thanks to the === x check and the r < y check below + Gadgets.rangeCheck16(q); + Gadgets.rangeCheck16(r); - let r_ = UInt8.from(r); - let q_ = UInt8.from(q); + let rest = UInt8.from(r); + let quotient = UInt8.from(q); - r_.assertLessThan(UInt8.from(y_)); - return { quotient: q_, rest: r_ }; + rest.assertLessThan(UInt8.from(y_)); + return { quotient, rest }; } /** From 5da4f727397ee15841ed2a97d1335f06af9321af Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 13:02:36 +0100 Subject: [PATCH 1104/1215] fix and polish uint8 --- src/lib/int.ts | 359 +++++++++++++++---------------------------------- 1 file changed, 108 insertions(+), 251 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index c5787c3143..a365b07919 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -4,7 +4,7 @@ import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; import { Gadgets } from './gadgets/gadgets.js'; -import { withMessage } from './field.js'; +import { FieldVar, withMessage } from './field.js'; import { chunkString } from './util/arrays.js'; // external API @@ -980,119 +980,103 @@ class UInt8 extends Struct({ * * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. */ - constructor(x: number | bigint | string | Field | UInt8) { - if (x instanceof UInt8) x = x.value; + constructor(x: number | bigint | string | FieldVar | UInt8) { + if (x instanceof UInt8) x = x.value.value; super({ value: Field(x) }); UInt8.checkConstant(this.value); } /** - * Add a {@link UInt8} value to another {@link UInt8} element. + * Add a {@link UInt8} to another {@link UInt8} without allowing overflow. * * @example * ```ts * const x = UInt8.from(3); - * const sum = x.add(UInt8.from(5)); - * - * sum.assertEquals(UInt8.from(8)); + * const sum = x.add(5); + * sum.assertEquals(8); * ``` * - * **Warning**: This operation cannot overflow past 255, an error is thrown if the result is greater than 255. - * - * @param value - a {@link UInt8} value to add to the {@link UInt8}. - * - * @return A {@link UInt8} element that is the sum of the two values. + * @throws if the result is greater than 255. */ - add(value: UInt8 | number) { - return UInt8.from(this.value.add(UInt8.from(value).value)); + add(y: UInt8 | bigint | number) { + let z = this.value.add(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.from(z); } /** - * Subtract a {@link UInt8} value by another {@link UInt8} element. + * Subtract a {@link UInt8} from another {@link UInt8} without allowing underflow. * * @example * ```ts * const x = UInt8.from(8); - * const difference = x.sub(UInt8.from(5)); - * - * difference.assertEquals(UInt8.from(3)); + * const difference = x.sub(5); + * difference.assertEquals(3); * ``` * - * @param value - a {@link UInt8} value to subtract from the {@link UInt8}. - * - * @return A {@link UInt8} element that is the difference of the two values. + * @throws if the result is less than 0. */ - sub(value: UInt8 | number) { - return UInt8.from(this.value.sub(UInt8.from(value).value)); + sub(y: UInt8 | bigint | number) { + let z = this.value.sub(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.from(z); } /** - * Multiply a {@link UInt8} value by another {@link UInt8} element. + * Multiply a {@link UInt8} by another {@link UInt8} without allowing overflow. * * @example * ```ts * const x = UInt8.from(3); - * const product = x.mul(UInt8.from(5)); - * - * product.assertEquals(UInt8.from(15)); + * const product = x.mul(5); + * product.assertEquals(15); * ``` * - * **Warning**: This operation cannot overflow past 255, an error is thrown if the result is greater than 255. - * - * @param value - a {@link UInt8} value to multiply with the {@link UInt8}. - * - * @return A {@link UInt8} element that is the product of the two values. + * @throws if the result is greater than 255. */ - mul(value: UInt8 | number) { - return UInt8.from(this.value.mul(UInt8.from(value).value)); + mul(y: UInt8 | bigint | number) { + let z = this.value.mul(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.from(z); } /** - * Divide a {@link UInt8} value by another {@link UInt8} element. - * - * Proves that the denominator is non-zero, or throws a "Division by zero" error. + * Divide a {@link UInt8} by another {@link UInt8}. + * This is integer division that rounds down. * * @example * ```ts - * const x = UInt8.from(6); - * const quotient = x.div(UInt8.from(3)); - * - * quotient.assertEquals(UInt8.from(2)); + * const x = UInt8.from(7); + * const quotient = x.div(2); + * quotient.assertEquals(3); * ``` - * - * @param value - a {@link UInt8} value to divide with the {@link UInt8}. - * - * @return A {@link UInt8} element that is the division of the two values. */ - div(value: UInt8 | number) { - return this.divMod(value).quotient; + div(y: UInt8 | bigint | number) { + return this.divMod(y).quotient; } /** - * Get the remainder a {@link UInt8} value of division of another {@link UInt8} element. + * Get the remainder a {@link UInt8} of division of another {@link UInt8}. * * @example * ```ts * const x = UInt8.from(50); - * const mod = x.mod(UInt8.from(30)); - * - * mod.assertEquals(UInt8.from(18)); + * const mod = x.mod(30); + * mod.assertEquals(20); * ``` - * - * @param value - a {@link UInt8} to get the modulus with another {@link UInt8}. - * - * @return A {@link UInt8} element that is the modulus of the two values. */ - mod(value: UInt8 | number) { - return this.divMod(value).rest; + mod(y: UInt8 | bigint | number) { + return this.divMod(y).remainder; } /** - * Get the quotient and remainder of a {@link UInt8} value divided by another {@link UInt8} element. + * Get the quotient and remainder of a {@link UInt8} divided by another {@link UInt8}: + * + * `x == y * q + r`, where `0 <= r < y`. * * @param y - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. * - * @return The quotient and remainder of the two values. + * @return The quotient `q` and remainder `r`. */ divMod(y: UInt8 | bigint | number) { let x = this.value; @@ -1103,7 +1087,7 @@ class UInt8 extends Struct({ let yn = y_.toBigInt(); let q = xn / yn; let r = xn - q * yn; - return { quotient: UInt8.from(q), rest: UInt8.from(r) }; + return { quotient: UInt8.from(q), remainder: UInt8.from(r) }; } // prove that x === q * y + r, where 0 <= r < y @@ -1115,77 +1099,60 @@ class UInt8 extends Struct({ Gadgets.rangeCheck16(q); Gadgets.rangeCheck16(r); - let rest = UInt8.from(r); + let remainder = UInt8.from(r); let quotient = UInt8.from(q); - rest.assertLessThan(UInt8.from(y_)); - return { quotient, rest }; + remainder.assertLessThan(y); + return { quotient, remainder }; } /** * Check if this {@link UInt8} is less than or equal to another {@link UInt8} value. - * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * Returns a {@link Bool}. * * @example * ```ts - * UInt8.from(3).lessThanOrEqual(UInt8.from(5)).assertEquals(Bool(true)); + * UInt8.from(3).lessThanOrEqual(UInt8.from(5)); * ``` - * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * - * - * @param y - the {@link UInt8} value to compare with this {@link UInt8}. - * - * @return A {@link Bool} representing if this {@link UInt8} is less than or equal another {@link UInt8} value. */ - lessThanOrEqual(y: UInt8): Bool { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() <= y.value.toBigInt()); + lessThanOrEqual(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() <= y_.toBigInt()); } throw Error('Not implemented'); } /** * Check if this {@link UInt8} is less than another {@link UInt8} value. - * Returns a {@link Bool}, which is a provable type and can be used prove to the validity of this statement. + * Returns a {@link Bool}. * * @example * ```ts - * UInt8.from(2).lessThan(UInt8.from(3)).assertEquals(Bool(true)); + * UInt8.from(2).lessThan(UInt8.from(3)); * ``` - * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * - * @param y - the {@link UInt8} value to compare with this {@link UInt8}. - * - * @return A {@link Bool} representing if this {@link UInt8} is less than another {@link UInt8} value. */ - lessThan(y: UInt8): Bool { - if (this.value.isConstant() && y.value.isConstant()) { - return Bool(this.value.toBigInt() < y.value.toBigInt()); + lessThan(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() < y_.toBigInt()); } throw Error('Not implemented'); } /** * Assert that this {@link UInt8} is less than another {@link UInt8} value. - * Calling this function is equivalent to `UInt8(...).lessThan(...).assertEquals(Bool(true))`. - * See {@link UInt8.lessThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThan(y: UInt8, message?: string) { - if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); + 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}`); @@ -1193,27 +1160,23 @@ class UInt8 extends Struct({ return; } // x < y <=> x + 1 <= y - let xPlus1 = new UInt8(this.value.add(1)); + let xPlus1 = new UInt8(this.value.add(1).value); xPlus1.assertLessThanOrEqual(y, message); } /** * Assert that this {@link UInt8} is less than or equal to another {@link UInt8} value. - * Calling this function is equivalent to `UInt8(...).lessThanOrEqual(...).assertEquals(Bool(true))`. - * See {@link UInt8.lessThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThanOrEqual(y: UInt8, message?: string) { - if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); + 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}`); @@ -1221,86 +1184,64 @@ class UInt8 extends Struct({ return; } try { - // x <= y <=> y - x >= 0 <=> y - x in [0, 2^8) - let yMinusX = y.value.sub(this.value).seal(); - Gadgets.rangeCheck8(yMinusX); + // x <= y <=> y - x >= 0 which is implied by y - x in [0, 2^16) + let yMinusX = y_.value.sub(this.value).seal(); + Gadgets.rangeCheck16(yMinusX); } catch (err) { throw withMessage(err, message); } } /** - * Check if this {@link UInt8} is greater than another {@link UInt8} value. - * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * Check if this {@link UInt8} is greater than another {@link UInt8}. + * Returns a {@link Bool}. * * @example * ```ts - * UInt8.from(5).greaterThan(UInt8.from(3)).assertEquals(Bool(true)); + * // 5 > 3 + * UInt8.from(5).greaterThan(3); * ``` - * - * **Warning**: Comparison methods currently only support Field elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * - * @param y - the {@link UInt8} value to compare with this {@link UInt8}. - * - * @return A {@link Bool} representing if this {@link UInt8} is greater than another {@link UInt8} value. */ - greaterThan(y: UInt8) { - return y.lessThan(this); + greaterThan(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThan(this); } /** * Check if this {@link UInt8} is greater than or equal another {@link UInt8} value. - * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * Returns a {@link Bool}. * * @example * ```ts - * UInt8.from(3).greaterThanOrEqual(UInt8.from(3)).assertEquals(Bool(true)); + * // 3 >= 3 + * UInt8.from(3).greaterThanOrEqual(3); * ``` - * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * - * @param y - the {@link UInt8} value to compare with this {@link Field}. - * - * @return A {@link Bool} representing if this {@link UInt8} is greater than or equal another {@link UInt8} value. */ - greaterThanOrEqual(y: UInt8) { - return this.lessThan(y).not(); + greaterThanOrEqual(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThanOrEqual(this); } /** * Assert that this {@link UInt8} is greater than another {@link UInt8} value. - * Calling this function is equivalent to `UInt8(...).greaterThan(...).assertEquals(Bool(true))`. - * See {@link UInt8.greaterThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertGreaterThan(y: UInt8, message?: string) { - y.assertLessThan(this, message); + assertGreaterThan(y: UInt8 | bigint | number, message?: string) { + UInt8.from(y).assertLessThan(this, message); } /** * Assert that this {@link UInt8} is greater than or equal to another {@link UInt8} value. - * Calling this function is equivalent to `UInt8(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. - * See {@link UInt8.greaterThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support UInt8 elements of size <= 8 bits in provable code. - * The method will throw if one of the inputs exceeds 8 bits. - * * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ assertGreaterThanOrEqual(y: UInt8, message?: string) { - y.assertLessThanOrEqual(this, message); + UInt8.from(y).assertLessThanOrEqual(this, message); } /** @@ -1311,23 +1252,15 @@ class UInt8 extends Struct({ * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertEquals(y: number | bigint | UInt8, message?: string) { - let y_ = new UInt8(y); - this.toField().assertEquals(y_.toField(), message); + assertEquals(y: UInt8 | bigint | number, message?: string) { + let y_ = UInt8.from(y); + this.value.assertEquals(y_.value, message); } /** * Serialize the {@link UInt8} to a string, e.g. for printing. * - * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link UInt8}. Use the operation only during debugging. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * console.log(someUInt8 .toString()); - * ``` - * - * @return A string equivalent to the string representation of the {@link UInt8}. + * **Warning**: This operation is not provable. */ toString() { return this.value.toString(); @@ -1336,74 +1269,23 @@ class UInt8 extends Struct({ /** * Serialize the {@link UInt8} to a bigint, e.g. for printing. * - * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the bigint representation of the {@link UInt8}. Use the operation only during debugging. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * console.log(someUInt8.toBigInt()); - * ``` - * - * @return A bigint equivalent to the bigint representation of the {@link UInt8}. + * **Warning**: This operation is not provable. */ toBigInt() { return this.value.toBigInt(); } /** - * Serialize the {@link UInt8} to a {@link Field}. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * console.log(someUInt8.toField()); - * ``` - * - * @return A {@link Field} equivalent to the bigint representation of the {@link UInt8}. + * {@link Provable.check} for {@link UInt8}. + * Proves that the input is in the [0, 255] range. */ - toField() { - return this.value; - } - - /** - * This function is the implementation of {@link Provable.check} in {@link UInt8} type. - * - * This function is called by {@link Provable.check} to check if the {@link UInt8} is valid. - * To check if a {@link UInt8} is valid, we need to check if the value fits in {@link UInt8.NUM_BITS} bits. - * - * @param value - the {@link UInt8} element to check. - */ - static check(x: { value: Field }) { + static check(x: { value: Field } | Field) { + if (x instanceof Field) x = { value: x }; Gadgets.rangeCheck8(x.value); } - /** - * Serialize the {@link UInt8} to a JSON string, e.g. for printing. - * - * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link UInt8}. Use the operation only during debugging. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * console.log(someUInt8 .toJSON()); - * ``` - * - * @return A string equivalent to the JSON representation of the {@link Field}. - */ - toJSON(): string { - return this.value.toString(); - } - /** * Turns a {@link UInt8} into a {@link UInt32}. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * const someUInt32 = someUInt8.toUInt32(); - * ``` - * - * @return A {@link UInt32} equivalent to the {@link UInt8}. */ toUInt32(): UInt32 { return new UInt32(this.value); @@ -1411,41 +1293,11 @@ class UInt8 extends Struct({ /** * Turns a {@link UInt8} into a {@link UInt64}. - * - * @example - * ```ts - * const someUInt8 = UInt8.from(42); - * const someUInt64 = someUInt8.toUInt64(); - * ``` - * - * @return A {@link UInt64} equivalent to the {@link UInt8}. - * */ + */ toUInt64(): UInt64 { return new UInt64(this.value); } - /** - * Check whether this {@link UInt8} element is a hard-coded constant in the constraint system. - * If a {@link UInt8} is constructed outside a zkApp method, it is a constant. - * - * @example - * ```ts - * console.log(UInt8.from(42).isConstant()); // true - * ``` - * - * @example - * ```ts - * \@method myMethod(x: UInt8) { - * console.log(x.isConstant()); // false - * } - * ``` - * - * @return A `boolean` showing if this {@link UInt8} is a constant or not. - */ - isConstant() { - return this.value.isConstant(); - } - // TODO: these might be better on a separate `Bytes` class static fromHex(xs: string): UInt8[] { let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); @@ -1459,21 +1311,26 @@ class UInt8 extends Struct({ * Creates a {@link UInt8} with a value of 255. */ static MAXINT() { - return new UInt8(Field((1n << BigInt(UInt8.NUM_BITS)) - 1n)); + return new UInt8((1n << BigInt(UInt8.NUM_BITS)) - 1n); } /** * Creates a new {@link UInt8}. */ static from(x: UInt8 | UInt64 | UInt32 | Field | number | bigint) { - if (x instanceof UInt64 || x instanceof UInt32 || x instanceof UInt8) - x = x.value; - return new UInt8(UInt8.checkConstant(Field(x))); + if (x instanceof UInt8) return x; + if (x instanceof UInt64 || x instanceof UInt32 || x instanceof Field) { + // if the input could be larger than 8 bits, we have to prove that it is not + let xx = x instanceof Field ? { value: x } : x; + UInt8.check(xx); + return new UInt8(xx.value.value); + } + return new UInt8(x); } private static checkConstant(x: Field) { - if (!x.isConstant()) return x; + if (!x.isConstant()) return x.value; Gadgets.rangeCheck8(x); - return x; + return x.value; } } From ac58ebd1092f9fbf7718a36ca112efcc15312b58 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 13 Dec 2023 13:32:30 +0100 Subject: [PATCH 1105/1215] add doc comments --- src/lib/keccak.ts | 85 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 9d99e9fd4f..c6f660d15c 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -7,15 +7,94 @@ import { Provable } from './provable.js'; export { Keccak }; const Keccak = { - /** TODO */ + /** + * Implementation of [NIST SHA-3](https://www.nist.gov/publications/sha-3-derived-functions-cshake-kmac-tuplehash-and-parallelhash) Hash Function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Functionality: + * - Applies the SHA-3 hash function to a list of byte-sized {@link Field} elements. + * - Flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * Input Expectations: + * - The function accepts a list of byte-sized {@link Field} elements as its input. + * - Input values should be range-checked externally before being passed to this function. + * + * Output Expectations: + * - Ensures that the hash output conforms to the chosen bit length. + * - The output is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - List of byte-sized {@link Field} elements representing the message to hash. + * + * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * + * ```ts + * let preimage = [5, 6, 19, 28, 19].map(Field); + * let digest256 = Keccak.nistSha3(256, preimage); + * let digest384 = Keccak.nistSha3(384, preimage); + * let digest512= Keccak.nistSha3(512, preimage); + * ``` + * + */ nistSha3(len: 256 | 384 | 512, message: Field[]): Field[] { return nistSha3(len, message); }, - /** TODO */ + /** + * Ethereum-Compatible Keccak-256 Hash Function. + * This is a specialized variant of {@link Keccak.preNist} configured for a 256-bit output length. + * + * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. + * + * Input Expectations: + * - Expects an input as a list of byte-sized {@link Field} elements. + * - The input should be range checked before calling this function, as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. + * + * Output Specifications: + * - Produces an output which is a list of byte-sized {@link Field} elements. + * - Ensures output is within the specified range using {@link Gadgets.rangeCheck8}. + * + * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * + * ```ts + * let preimage = [5, 6, 19, 28, 19].map(Field); + * let digest = Keccak.ethereum(preimage); + * ``` + */ ethereum(message: Field[]): Field[] { return ethereum(message); }, - /** TODO */ + /** + * Implementation of [pre-NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Pre-NIST SHA-3 is a variant of the Keccak hash function, which was standardized by NIST in 2015. + * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. + * + * Functionality: + * - Applies the pre-SHA-3 hash function to a list of byte-sized {@link Field} elements. + * - Flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * Input Expectations: + * - The function accepts a list of byte-sized {@link Field} elements as its input. + * - Input values should be range-checked externally before being passed to this function. + * + * Output Expectations: + * - Ensures that the hash output conforms to the chosen bit length. + * - The output is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - List of byte-sized {@link Field} elements representing the message to hash. + * + * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * + * ```ts + * let preimage = [5, 6, 19, 28, 19].map(Field); + * let digest256 = Keccak.preNist(256, preimage); + * let digest384 = Keccak.preNist(384, preimage); + * let digest512= Keccak.preNist(512, preimage); + * ``` + * + */ preNist(len: 256 | 384 | 512, message: Field[]): Field[] { return preNist(len, message); }, From 025cc9aaf806c3ec9e82b286afd47e49bdea13da Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Wed, 13 Dec 2023 13:37:55 +0100 Subject: [PATCH 1106/1215] minor --- src/lib/keccak.ts | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index c6f660d15c..c71c5b3730 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -11,17 +11,11 @@ const Keccak = { * Implementation of [NIST SHA-3](https://www.nist.gov/publications/sha-3-derived-functions-cshake-kmac-tuplehash-and-parallelhash) Hash Function. * Supports output lengths of 256, 384, or 512 bits. * - * Functionality: - * - Applies the SHA-3 hash function to a list of byte-sized {@link Field} elements. - * - Flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * Applies the SHA-3 hash function to a list of byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. * - * Input Expectations: - * - The function accepts a list of byte-sized {@link Field} elements as its input. - * - Input values should be range-checked externally before being passed to this function. + * The function accepts a list of byte-sized {@link Field} elements as its input. However, the input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * - * Output Expectations: - * - Ensures that the hash output conforms to the chosen bit length. - * - The output is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * The output is ensured to conform to the chosen bit length and is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. * @param message - List of byte-sized {@link Field} elements representing the message to hash. @@ -45,13 +39,10 @@ const Keccak = { * * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. * - * Input Expectations: - * - Expects an input as a list of byte-sized {@link Field} elements. - * - The input should be range checked before calling this function, as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. + * The function expects an input as a list of byte-sized {@link Field} elements. However, the input should be range checked before calling this function, + * as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. * - * Output Specifications: - * - Produces an output which is a list of byte-sized {@link Field} elements. - * - Ensures output is within the specified range using {@link Gadgets.rangeCheck8}. + * Produces an output which is a list of byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. * * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. * @@ -70,17 +61,11 @@ const Keccak = { * Pre-NIST SHA-3 is a variant of the Keccak hash function, which was standardized by NIST in 2015. * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. * - * Functionality: - * - Applies the pre-SHA-3 hash function to a list of byte-sized {@link Field} elements. - * - Flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * The function applies the pre-SHA-3 hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. * - * Input Expectations: - * - The function accepts a list of byte-sized {@link Field} elements as its input. - * - Input values should be range-checked externally before being passed to this function. + * {@link Keccak.preNist} accepts a list of byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * - * Output Expectations: - * - Ensures that the hash output conforms to the chosen bit length. - * - The output is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * The hash output is ensured to conform to the chosen bit length and is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. * @param message - List of byte-sized {@link Field} elements representing the message to hash. From 7caffae60c427d4325c7d607a709dbe27d1aa920 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 13:41:38 +0100 Subject: [PATCH 1107/1215] provable bytes type --- src/lib/int.ts | 22 +++-- src/lib/provable-types/bytes.ts | 103 +++++++++++++++++++++++ src/lib/provable-types/provable-types.ts | 18 ++++ 3 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 src/lib/provable-types/bytes.ts create mode 100644 src/lib/provable-types/provable-types.ts diff --git a/src/lib/int.ts b/src/lib/int.ts index a365b07919..a4cc974c4c 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -5,8 +5,6 @@ import { HashInput } from './hash.js'; import { Provable } from './provable.js'; import { Gadgets } from './gadgets/gadgets.js'; import { FieldVar, withMessage } from './field.js'; -import { chunkString } from './util/arrays.js'; - // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -1267,7 +1265,16 @@ class UInt8 extends Struct({ } /** - * Serialize the {@link UInt8} to a bigint, e.g. for printing. + * Serialize the {@link UInt8} to a number. + * + * **Warning**: This operation is not provable. + */ + toNumber() { + return Number(this.value.toBigInt()); + } + + /** + * Serialize the {@link UInt8} to a bigint. * * **Warning**: This operation is not provable. */ @@ -1298,15 +1305,6 @@ class UInt8 extends Struct({ return new UInt64(this.value); } - // TODO: these might be better on a separate `Bytes` class - static fromHex(xs: string): UInt8[] { - let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); - return bytes.map(UInt8.from); - } - static toHex(xs: UInt8[]): string { - return xs.map((x) => x.toBigInt().toString(16).padStart(2, '0')).join(''); - } - /** * Creates a {@link UInt8} with a value of 255. */ diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts new file mode 100644 index 0000000000..ce678970c0 --- /dev/null +++ b/src/lib/provable-types/bytes.ts @@ -0,0 +1,103 @@ +import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; +import { ProvablePureExtended } from '../circuit_value.js'; +import { assert } from '../gadgets/common.js'; +import { chunkString } from '../util/arrays.js'; +import { Provable } from '../provable.js'; +import { UInt8 } from '../int.js'; + +export { Bytes, createBytes }; + +/** + * A provable type representing an array of bytes. + */ +class Bytes { + data: UInt8[]; + + constructor(data: UInt8[]) { + let size = (this.constructor as typeof Bytes).size; + + // assert that data is not too long + assert( + data.length < size, + `Expected at most ${size} bytes, got ${data.length}` + ); + + // pad the data with zeros + let padding = Array.from( + { length: size - data.length }, + () => new UInt8(0) + ); + this.data = data.concat(padding); + } + + /** + * Coerce the input to {@link Bytes}. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static from(data: (UInt8 | bigint | number)[] | Uint8Array) { + return new this([...data].map(UInt8.from)); + } + + toBytes(): Uint8Array { + return Uint8Array.from(this.data.map((x) => x.toNumber())); + } + + /** + * Create {@link Bytes} from a string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromString(s: string) { + let bytes = new TextEncoder().encode(s); + return this.from(bytes); + } + + /** + * Create {@link Bytes} from a hex string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromHex(xs: string): Bytes { + let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); + return this.from(bytes); + } + + /** + * Convert {@link Bytes} to a hex string. + */ + toHex(xs: Bytes): string { + return xs.data + .map((x) => x.toBigInt().toString(16).padStart(2, '0')) + .join(''); + } + + // dynamic subclassing infra + static _size?: number; + static _provable?: ProvablePureExtended; + + /** + * The size of the {@link Bytes}. + */ + static get size() { + assert(this._size !== undefined, 'Bytes not initialized'); + return this._size; + } + + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'Bytes not initialized'); + return this._provable; + } +} + +function createBytes(size: number): typeof Bytes { + return class Bytes_ extends Bytes { + static _size = size; + static _provable = provableFromClass(Bytes_, { + data: Provable.Array(UInt8, size), + }); + }; +} diff --git a/src/lib/provable-types/provable-types.ts b/src/lib/provable-types/provable-types.ts new file mode 100644 index 0000000000..717b9b0988 --- /dev/null +++ b/src/lib/provable-types/provable-types.ts @@ -0,0 +1,18 @@ +import { Bytes as InternalBytes, createBytes } from './bytes.js'; + +export { Bytes }; + +type Bytes = InternalBytes; + +/** + * A provable type representing an array of bytes. + * + * ```ts + * class Bytes32 extends Bytes(32) {} + * + * let bytes = Bytes32.fromHex('deadbeef'); + * ``` + */ +function Bytes(size: number) { + return createBytes(size); +} From fca04f80b0890d4d6bdae2ecb4178c9be128e328 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 14:05:30 +0100 Subject: [PATCH 1108/1215] use bytes as keccak input --- src/lib/keccak.ts | 64 ++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index a1e4bb8728..1b6f48a31b 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -1,23 +1,24 @@ import { Field } from './field.js'; import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; -import { rangeCheck8 } from './gadgets/range-check.js'; import { Provable } from './provable.js'; import { chunk } from './util/arrays.js'; +import { Bytes } from './provable-types/provable-types.js'; +import { UInt8 } from './int.js'; export { Keccak }; const Keccak = { /** TODO */ - nistSha3(len: 256 | 384 | 512, message: Field[]): Field[] { + nistSha3(len: 256 | 384 | 512, message: Bytes) { return nistSha3(len, message); }, /** TODO */ - ethereum(message: Field[]): Field[] { + ethereum(message: Bytes) { return ethereum(message); }, /** TODO */ - preNist(len: 256 | 384 | 512, message: Field[]): Field[] { + preNist(len: 256 | 384 | 512, message: Bytes) { return preNist(len, message); }, }; @@ -102,7 +103,7 @@ function bytesToPad(rate: number, length: number): number { // The padded message will start with the message argument followed by the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). // If nist is true, then the padding rule is 0x06 ..0*..1. // If nist is false, then the padding rule is 10*1. -function pad(message: Field[], rate: number, nist: boolean): Field[] { +function pad(message: UInt8[], rate: number, nist: boolean): UInt8[] { // Find out desired length of the padding in bytes // If message is already rate bits, need to pad full rate again const extraBytes = bytesToPad(rate, message.length); @@ -112,8 +113,8 @@ function pad(message: Field[], rate: number, nist: boolean): Field[] { const last = 0x80n; // Create the padding vector - const pad = Array(extraBytes).fill(Field.from(0)); - pad[0] = Field.from(first); + const pad = Array(extraBytes).fill(UInt8.from(0)); + pad[0] = UInt8.from(first); pad[extraBytes - 1] = pad[extraBytes - 1].add(last); // Return the padded message @@ -324,11 +325,11 @@ function sponge( // - the 10*1 pad will take place after the message, until reaching the bit length rate. // - then, {0} pad will take place to finish the 200 bytes of the state. function hash( - message: Field[], + message: Bytes, length: number, capacity: number, nistVersion: boolean -): Field[] { +): UInt8[] { // Throw errors if used improperly assert(capacity > 0, 'capacity must be positive'); assert( @@ -346,7 +347,7 @@ function hash( const rate = KECCAK_STATE_LENGTH_WORDS - capacity; // apply padding, convert to words, and hash - const paddedBytes = pad(message, rate * BYTES_PER_WORD, nistVersion); + const paddedBytes = pad(message.data, rate * BYTES_PER_WORD, nistVersion); const padded = bytesToWords(paddedBytes); const hash = sponge(padded, length, capacity, rate); @@ -356,18 +357,20 @@ function hash( } // Gadget for NIST SHA-3 function for output lengths 256/384/512. -function nistSha3(len: 256 | 384 | 512, message: Field[]): Field[] { - return hash(message, len / 8, len / 4, true); +function nistSha3(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, true); + return BytesOfBitlength[len].from(bytes); } // Gadget for pre-NIST SHA-3 function for output lengths 256/384/512. // Note that when calling with output length 256 this is equivalent to the ethereum function -function preNist(len: 256 | 384 | 512, message: Field[]): Field[] { - return hash(message, len / 8, len / 4, false); +function preNist(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, false); + return BytesOfBitlength[len].from(bytes); } // Gadget for Keccak hash function for the parameters used in Ethereum. -function ethereum(message: Field[]): Field[] { +function ethereum(message: Bytes): Bytes { return preNist(256, message); } @@ -428,27 +431,36 @@ const State = { }, }; +// AUXILIARY TYPES + +class Bytes32 extends Bytes(32) {} +class Bytes48 extends Bytes(48) {} +class Bytes64 extends Bytes(64) {} + +const BytesOfBitlength = { + 256: Bytes32, + 384: Bytes48, + 512: Bytes64, +}; + // AUXILARY FUNCTIONS // Auxiliary functions to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it -function bytesToWord(wordBytes: Field[]): Field { - return wordBytes.reduce((acc, value, idx) => { +function bytesToWord(wordBytes: UInt8[]): Field { + return wordBytes.reduce((acc, byte, idx) => { const shift = 1n << BigInt(8 * idx); - return acc.add(value.mul(shift)); + return acc.add(byte.value.mul(shift)); }, Field.from(0)); } -function wordToBytes(word: Field): Field[] { - let bytes = Provable.witness(Provable.Array(Field, BYTES_PER_WORD), () => { +function wordToBytes(word: Field): UInt8[] { + let bytes = Provable.witness(Provable.Array(UInt8, BYTES_PER_WORD), () => { let w = word.toBigInt(); return Array.from({ length: BYTES_PER_WORD }, (_, k) => - Field.from((w >> BigInt(8 * k)) & 0xffn) + UInt8.from((w >> BigInt(8 * k)) & 0xffn) ); }); - // range-check - // TODO(jackryanservia): Use lookup argument once issue is resolved - bytes.forEach(rangeCheck8); // check decomposition bytesToWord(bytes).assertEquals(word); @@ -456,11 +468,11 @@ function wordToBytes(word: Field): Field[] { return bytes; } -function bytesToWords(bytes: Field[]): Field[] { +function bytesToWords(bytes: UInt8[]): Field[] { return chunk(bytes, BYTES_PER_WORD).map(bytesToWord); } -function wordsToBytes(words: Field[]): Field[] { +function wordsToBytes(words: Field[]): UInt8[] { return words.flatMap(wordToBytes); } From da919b70b6fbd4c513198bf45617693f19f11147 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:00:10 +0100 Subject: [PATCH 1109/1215] tweaks to bytes type --- src/lib/provable-types/bytes.ts | 38 ++++++++++++++++-------- src/lib/provable-types/provable-types.ts | 1 + 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index ce678970c0..7cb3763d74 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -5,29 +5,31 @@ import { chunkString } from '../util/arrays.js'; import { Provable } from '../provable.js'; import { UInt8 } from '../int.js'; -export { Bytes, createBytes }; +export { Bytes, createBytes, FlexibleBytes }; + +type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; /** * A provable type representing an array of bytes. */ class Bytes { - data: UInt8[]; + bytes: UInt8[]; - constructor(data: UInt8[]) { + constructor(bytes: UInt8[]) { let size = (this.constructor as typeof Bytes).size; // assert that data is not too long assert( - data.length < size, - `Expected at most ${size} bytes, got ${data.length}` + bytes.length < size, + `Expected at most ${size} bytes, got ${bytes.length}` ); // pad the data with zeros let padding = Array.from( - { length: size - data.length }, + { length: size - bytes.length }, () => new UInt8(0) ); - this.data = data.concat(padding); + this.bytes = bytes.concat(padding); } /** @@ -35,12 +37,17 @@ class Bytes { * * Inputs smaller than `this.size` are padded with zero bytes. */ - static from(data: (UInt8 | bigint | number)[] | Uint8Array) { + static from(data: (UInt8 | bigint | number)[] | Uint8Array | Bytes): Bytes { + if (data instanceof Bytes) return data; + if (this._size === undefined) { + let Bytes_ = createBytes(data.length); + return Bytes_.from(data); + } return new this([...data].map(UInt8.from)); } toBytes(): Uint8Array { - return Uint8Array.from(this.data.map((x) => x.toNumber())); + return Uint8Array.from(this.bytes.map((x) => x.toNumber())); } /** @@ -67,14 +74,17 @@ class Bytes { * Convert {@link Bytes} to a hex string. */ toHex(xs: Bytes): string { - return xs.data + return xs.bytes .map((x) => x.toBigInt().toString(16).padStart(2, '0')) .join(''); } // dynamic subclassing infra static _size?: number; - static _provable?: ProvablePureExtended; + static _provable?: ProvablePureExtended< + Bytes, + { bytes: { value: string }[] } + >; /** * The size of the {@link Bytes}. @@ -84,6 +94,10 @@ class Bytes { return this._size; } + get length() { + return this.bytes.length; + } + /** * `Provable` */ @@ -97,7 +111,7 @@ function createBytes(size: number): typeof Bytes { return class Bytes_ extends Bytes { static _size = size; static _provable = provableFromClass(Bytes_, { - data: Provable.Array(UInt8, size), + bytes: Provable.Array(UInt8, size), }); }; } diff --git a/src/lib/provable-types/provable-types.ts b/src/lib/provable-types/provable-types.ts index 717b9b0988..1d850aa3c5 100644 --- a/src/lib/provable-types/provable-types.ts +++ b/src/lib/provable-types/provable-types.ts @@ -16,3 +16,4 @@ type Bytes = InternalBytes; function Bytes(size: number) { return createBytes(size); } +Bytes.from = InternalBytes.from; From 3d8dbbd9f9695d1b15549a5cc5a5c10f628ed96e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:01:18 +0100 Subject: [PATCH 1110/1215] adapt bytes consumers --- src/lib/foreign-ecdsa.ts | 19 ++++++++++--------- src/lib/keccak.ts | 17 +++++++++-------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index d5d941745c..bccbaa77ab 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -11,9 +11,10 @@ import { AlmostForeignField } from './foreign-field.js'; import { assert } from './gadgets/common.js'; import { Field3 } from './gadgets/foreign-field.js'; import { Ecdsa } from './gadgets/elliptic-curve.js'; -import { Field } from './field.js'; import { l } from './gadgets/range-check.js'; import { Keccak } from './keccak.js'; +import { Bytes } from './provable-types/provable-types.js'; +import { UInt8 } from './int.js'; // external API export { createEcdsa, EcdsaSignature }; @@ -99,7 +100,7 @@ class EcdsaSignature { * isValid.assertTrue('signature verifies'); * ``` */ - verify(message: Field[], publicKey: FlexiblePoint) { + verify(message: Bytes, publicKey: FlexiblePoint) { let msgHashBytes = Keccak.ethereum(message); let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); return this.verifySignedHash(msgHash, publicKey); @@ -132,8 +133,7 @@ class EcdsaSignature { * Note: This method is not provable, and only takes JS bigints as input. */ static sign(message: (bigint | number)[] | Uint8Array, privateKey: bigint) { - let msgFields = [...message].map(Field.from); - let msgHashBytes = Keccak.ethereum(msgFields); + let msgHashBytes = Keccak.ethereum(message); let msgHash = keccakOutputToScalar(msgHashBytes, this.Curve); return this.signHash(msgHash.toBigInt(), privateKey); } @@ -228,7 +228,7 @@ function toObject(signature: EcdsaSignature) { * - takes a 32 bytes hash * - converts them to 3 limbs which collectively have L_n <= 256 bits */ -function keccakOutputToScalar(hash: Field[], Curve: typeof ForeignCurve) { +function keccakOutputToScalar(hash: Bytes, Curve: typeof ForeignCurve) { const L_n = Curve.Scalar.sizeInBits; // keep it simple for now, avoid dealing with dropping bits // TODO: what does "leftmost bits" mean? big-endian or little-endian? @@ -240,14 +240,15 @@ function keccakOutputToScalar(hash: Field[], Curve: typeof ForeignCurve) { // piece together into limbs // bytes are big-endian, so the first byte is the most significant assert(l === 88n); - let x2 = bytesToLimbBE(hash.slice(0, 10)); - let x1 = bytesToLimbBE(hash.slice(10, 21)); - let x0 = bytesToLimbBE(hash.slice(21, 32)); + let x2 = bytesToLimbBE(hash.bytes.slice(0, 10)); + let x1 = bytesToLimbBE(hash.bytes.slice(10, 21)); + let x0 = bytesToLimbBE(hash.bytes.slice(21, 32)); return new Curve.Scalar.AlmostReduced([x0, x1, x2]); } -function bytesToLimbBE(bytes: Field[]) { +function bytesToLimbBE(bytes_: UInt8[]) { + let bytes = bytes_.map((x) => x.value); let n = bytes.length; let limb = bytes[0]; for (let i = 1; i < n; i++) { diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 1b6f48a31b..5def838e39 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -3,23 +3,24 @@ import { Gadgets } from './gadgets/gadgets.js'; import { assert } from './errors.js'; import { Provable } from './provable.js'; import { chunk } from './util/arrays.js'; -import { Bytes } from './provable-types/provable-types.js'; +import { FlexibleBytes } from './provable-types/bytes.js'; import { UInt8 } from './int.js'; +import { Bytes } from './provable-types/provable-types.js'; export { Keccak }; const Keccak = { /** TODO */ - nistSha3(len: 256 | 384 | 512, message: Bytes) { - return nistSha3(len, message); + nistSha3(len: 256 | 384 | 512, message: FlexibleBytes) { + return nistSha3(len, Bytes.from(message)); }, /** TODO */ - ethereum(message: Bytes) { - return ethereum(message); + ethereum(message: FlexibleBytes) { + return ethereum(Bytes.from(message)); }, /** TODO */ - preNist(len: 256 | 384 | 512, message: Bytes) { - return preNist(len, message); + preNist(len: 256 | 384 | 512, message: FlexibleBytes) { + return preNist(len, Bytes.from(message)); }, }; @@ -347,7 +348,7 @@ function hash( const rate = KECCAK_STATE_LENGTH_WORDS - capacity; // apply padding, convert to words, and hash - const paddedBytes = pad(message.data, rate * BYTES_PER_WORD, nistVersion); + const paddedBytes = pad(message.bytes, rate * BYTES_PER_WORD, nistVersion); const padded = bytesToWords(paddedBytes); const hash = sponge(padded, length, capacity, rate); From 644f2790bc93a121d44fc8efa2b6bfb63f8be9b3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:06:14 +0100 Subject: [PATCH 1111/1215] fix import cycle --- src/index.ts | 13 +++++++++++-- src/lib/hash.ts | 17 +++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4b896dd901..3f29a8059f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,8 +9,17 @@ export { } from './lib/foreign-field.js'; export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; -export { Poseidon, TokenSymbol, Hash } from './lib/hash.js'; -export { Keccak } from './lib/keccak.js'; +export { TokenSymbol } from './lib/hash.js'; + +import { Poseidon } from './lib/hash.js'; +import { Keccak } from './lib/keccak.js'; + +const Hash = { + hash: Poseidon.hash, + Poseidon, + Keccak, +}; +export { Poseidon, Keccak, Hash }; export * from './lib/signature.js'; export type { diff --git a/src/lib/hash.ts b/src/lib/hash.ts index d986d7ea47..39fd993774 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -6,10 +6,9 @@ import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; -import { Keccak } from './keccak.js'; // external API -export { Poseidon, TokenSymbol, Hash }; +export { Poseidon, TokenSymbol }; // internal API export { @@ -24,19 +23,19 @@ export { }; class Sponge { - private sponge: unknown; + #sponge: unknown; constructor() { let isChecked = Provable.inCheckedComputation(); - this.sponge = Snarky.poseidon.sponge.create(isChecked); + this.#sponge = Snarky.poseidon.sponge.create(isChecked); } absorb(x: Field) { - Snarky.poseidon.sponge.absorb(this.sponge, x.value); + Snarky.poseidon.sponge.absorb(this.#sponge, x.value); } squeeze(): Field { - return Field(Snarky.poseidon.sponge.squeeze(this.sponge)); + return Field(Snarky.poseidon.sponge.squeeze(this.#sponge)); } } @@ -206,9 +205,3 @@ function isConstant(fields: Field[]) { function toBigints(fields: Field[]) { return fields.map((x) => x.toBigInt()); } - -const Hash = { - hash: Poseidon.hash, - Poseidon, - Keccak, -}; From eadb423e24e91264bf94e4bb924d6569c9604b31 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:06:32 +0100 Subject: [PATCH 1112/1215] adapt keccak unit test --- src/lib/keccak.unit-test.ts | 67 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 7caa5c166a..3b1b163772 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,9 +1,7 @@ -import { Field } from './field.js'; -import { Provable } from './provable.js'; import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; import { Random } from './testing/random.js'; -import { array, equivalentAsync, fieldWithRng } from './testing/equivalent.js'; +import { equivalentAsync, spec } from './testing/equivalent.js'; import { keccak_224, keccak_256, @@ -14,6 +12,7 @@ import { sha3_384, sha3_512, } from '@noble/hashes/sha3'; +import { Bytes } from './provable-types/provable-types.js'; const RUNS = 1; @@ -32,8 +31,6 @@ const testImplementations = { }, }; -const uint = (length: number) => fieldWithRng(Random.biguint(length)); - // Choose a test length at random const digestLength = ([256, 384, 512] as const)[Math.floor(Math.random() * 4)]; @@ -46,18 +43,18 @@ const preImageLength = Math.floor(digestLength / (Math.random() * 4 + 2)); // No need to test Ethereum because it's just a special case of preNist const KeccakProgram = ZkProgram({ name: 'keccak-test', - publicInput: Provable.Array(Field, preImageLength), - publicOutput: Provable.Array(Field, digestLengthBytes), + publicInput: Bytes(preImageLength).provable, + publicOutput: Bytes(digestLengthBytes).provable, methods: { nistSha3: { privateInputs: [], - method(preImage) { + method(preImage: Bytes) { return Keccak.nistSha3(digestLength, preImage); }, }, preNist: { privateInputs: [], - method(preImage) { + method(preImage: Bytes) { return Keccak.preNist(digestLength, preImage); }, }, @@ -66,42 +63,38 @@ const KeccakProgram = ZkProgram({ await KeccakProgram.compile(); +const bytes = (length: number) => { + const Bytes_ = Bytes(length); + return spec({ + rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), + there: Bytes_.from, + back: (x) => x.toBytes(), + provable: Bytes_.provable, + }); +}; + // SHA-3 await equivalentAsync( { - from: [array(uint(8), preImageLength)], - to: array(uint(8), digestLengthBytes), + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), }, { runs: RUNS } -)( - (x) => { - const byteArray = new Uint8Array(x.map(Number)); - const result = testImplementations.sha3[digestLength](byteArray); - return Array.from(result).map(BigInt); - }, - async (x) => { - const proof = await KeccakProgram.nistSha3(x); - await KeccakProgram.verify(proof); - return proof.publicOutput; - } -); +)(testImplementations.sha3[digestLength], async (x) => { + const proof = await KeccakProgram.nistSha3(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); // PreNIST Keccak await equivalentAsync( { - from: [array(uint(8), preImageLength)], - to: array(uint(8), digestLengthBytes), + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), }, { runs: RUNS } -)( - (x) => { - const byteArray = new Uint8Array(x.map(Number)); - const result = testImplementations.preNist[digestLength](byteArray); - return Array.from(result).map(BigInt); - }, - async (x) => { - const proof = await KeccakProgram.preNist(x); - await KeccakProgram.verify(proof); - return proof.publicOutput; - } -); +)(testImplementations.preNist[digestLength], async (x) => { + const proof = await KeccakProgram.preNist(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); From 3ee7d192e3887ee639ae97574c6b0649bfd271d4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:15:38 +0100 Subject: [PATCH 1113/1215] fix length assertion --- src/lib/provable-types/bytes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index 7cb3763d74..8c0b08b289 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -20,7 +20,7 @@ class Bytes { // assert that data is not too long assert( - bytes.length < size, + bytes.length <= size, `Expected at most ${size} bytes, got ${bytes.length}` ); From 8b4344a4d3da49ef9031c4c1a86225915274f63a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:20:37 +0100 Subject: [PATCH 1114/1215] fix something very stupid --- src/lib/keccak.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 3b1b163772..7aa16f024f 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -32,7 +32,7 @@ const testImplementations = { }; // Choose a test length at random -const digestLength = ([256, 384, 512] as const)[Math.floor(Math.random() * 4)]; +const digestLength = ([256, 384, 512] as const)[Math.floor(Math.random() * 3)]; // Digest length in bytes const digestLengthBytes = digestLength / 8; From 0a896bd7f6fc765aa4bb9b3fcb11e8583c6d5c8b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:32:47 +0100 Subject: [PATCH 1115/1215] add constant tests --- src/lib/keccak.unit-test.ts | 53 ++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 7aa16f024f..454874cdcb 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,7 +1,11 @@ import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; -import { Random } from './testing/random.js'; -import { equivalentAsync, spec } from './testing/equivalent.js'; +import { Random, sample } from './testing/random.js'; +import { + equivalentAsync, + equivalentProvable, + spec, +} from './testing/equivalent.js'; import { keccak_224, keccak_256, @@ -31,8 +35,41 @@ const testImplementations = { }, }; +const lengths = [256, 384, 512] as const; + +// witness construction checks + +const bytes = (length: number) => { + const Bytes_ = Bytes(length); + return spec({ + rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), + there: Bytes_.from, + back: (x) => x.toBytes(), + provable: Bytes_.provable, + }); +}; + +for (let length of lengths) { + let [preimageLength] = sample(Random.nat(100), 1); + console.log(`Testing ${length} with preimage length ${preimageLength}`); + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(length / 8); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.sha3[length], + (x) => Keccak.nistSha3(length, x), + `sha3 ${length}` + ); + + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.preNist[length], + (x) => Keccak.preNist(length, x), + `keccak ${length}` + ); +} + // Choose a test length at random -const digestLength = ([256, 384, 512] as const)[Math.floor(Math.random() * 3)]; +const digestLength = lengths[Math.floor(Math.random() * 3)]; // Digest length in bytes const digestLengthBytes = digestLength / 8; @@ -63,16 +100,6 @@ const KeccakProgram = ZkProgram({ await KeccakProgram.compile(); -const bytes = (length: number) => { - const Bytes_ = Bytes(length); - return spec({ - rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), - there: Bytes_.from, - back: (x) => x.toBytes(), - provable: Bytes_.provable, - }); -}; - // SHA-3 await equivalentAsync( { From 5d151417bc1e22ecdb2931e43524b3c4e53ef085 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:53:02 +0100 Subject: [PATCH 1116/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index c71c5b3730..ef71911f11 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -8,7 +8,7 @@ export { Keccak }; const Keccak = { /** - * Implementation of [NIST SHA-3](https://www.nist.gov/publications/sha-3-derived-functions-cshake-kmac-tuplehash-and-parallelhash) Hash Function. + * Implementation of [NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. * Supports output lengths of 256, 384, or 512 bits. * * Applies the SHA-3 hash function to a list of byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. From aa1bb04fe871a134e9e4730138885b57e7fcf450 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:53:09 +0100 Subject: [PATCH 1117/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index ef71911f11..5849c06dbd 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -26,7 +26,7 @@ const Keccak = { * let preimage = [5, 6, 19, 28, 19].map(Field); * let digest256 = Keccak.nistSha3(256, preimage); * let digest384 = Keccak.nistSha3(384, preimage); - * let digest512= Keccak.nistSha3(512, preimage); + * let digest512 = Keccak.nistSha3(512, preimage); * ``` * */ From 25bcd0c7d688d2bb2844700f54091dbb1537c03e Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:53:21 +0100 Subject: [PATCH 1118/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 5849c06dbd..458bbb491c 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -11,7 +11,7 @@ const Keccak = { * Implementation of [NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. * Supports output lengths of 256, 384, or 512 bits. * - * Applies the SHA-3 hash function to a list of byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * Applies the SHA-3 hash function to a list of big-endian byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. * * The function accepts a list of byte-sized {@link Field} elements as its input. However, the input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * From bb4f5bdaa74495a8e24de43f11ceccdd840134e5 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:53:30 +0100 Subject: [PATCH 1119/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 458bbb491c..c1c2a2b02a 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -15,7 +15,7 @@ const Keccak = { * * The function accepts a list of byte-sized {@link Field} elements as its input. However, the input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * - * The output is ensured to conform to the chosen bit length and is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * The output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. * @param message - List of byte-sized {@link Field} elements representing the message to hash. From 008df1899cdcf82cf288ea13e6b202a2f90b20ae Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:53:49 +0100 Subject: [PATCH 1120/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index c1c2a2b02a..24ee74ec7e 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -39,7 +39,7 @@ const Keccak = { * * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. * - * The function expects an input as a list of byte-sized {@link Field} elements. However, the input should be range checked before calling this function, + * The function expects an input as a list of big-endian byte-sized {@link Field} elements. However, the input should be range checked before calling this function, * as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. * * Produces an output which is a list of byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. From fc7a8616603325184d4a1c8f902aab61ae278b0c Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:55:45 +0100 Subject: [PATCH 1121/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 24ee74ec7e..190abdcc9a 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -42,7 +42,7 @@ const Keccak = { * The function expects an input as a list of big-endian byte-sized {@link Field} elements. However, the input should be range checked before calling this function, * as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. * - * Produces an output which is a list of byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. + * Produces an output which is a list of big-endian byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. * * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. * From 0651999914f9f32c6812f35799bf87d539d47193 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:55:57 +0100 Subject: [PATCH 1122/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 190abdcc9a..559e4422c1 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -61,7 +61,7 @@ const Keccak = { * Pre-NIST SHA-3 is a variant of the Keccak hash function, which was standardized by NIST in 2015. * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. * - * The function applies the pre-SHA-3 hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. * * {@link Keccak.preNist} accepts a list of byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * From 542ce92d3679af35ed7c01131de69cd30f052967 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:56:03 +0100 Subject: [PATCH 1123/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 559e4422c1..04a01f565b 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -65,7 +65,7 @@ const Keccak = { * * {@link Keccak.preNist} accepts a list of byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * - * The hash output is ensured to conform to the chosen bit length and is a list of byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * The hash output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. * @param message - List of byte-sized {@link Field} elements representing the message to hash. From 1e390260f6e294859ba689e9cfd7fd2c5d11b0a8 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:56:15 +0100 Subject: [PATCH 1124/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 04a01f565b..149c1e075d 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -44,6 +44,8 @@ const Keccak = { * * Produces an output which is a list of big-endian byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. * + * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. + * * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. * * ```ts From 82e58473a0788c91105be77eb327c59de54ab521 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:56:21 +0100 Subject: [PATCH 1125/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 149c1e075d..e646628491 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -18,7 +18,7 @@ const Keccak = { * The output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. - * @param message - List of byte-sized {@link Field} elements representing the message to hash. + * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. * * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. * From 51226affeafdfdabb2b9b4714e6bbfb4e9fb1376 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:56:27 +0100 Subject: [PATCH 1126/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index e646628491..6e38f73946 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -70,7 +70,7 @@ const Keccak = { * The hash output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. - * @param message - List of byte-sized {@link Field} elements representing the message to hash. + * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. * * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. * From b97b1aac7e490ee65c182cc23ba74de2af20cb4b Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:56:43 +0100 Subject: [PATCH 1127/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 6e38f73946..6d2d20b05a 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -57,7 +57,7 @@ const Keccak = { return ethereum(message); }, /** - * Implementation of [pre-NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. + * Implementation of [pre-NIST Keccak](https://keccak.team/keccak.html) hash function. * Supports output lengths of 256, 384, or 512 bits. * * Pre-NIST SHA-3 is a variant of the Keccak hash function, which was standardized by NIST in 2015. From ad69dbb76b0370bfbcb70eb239d540956546f791 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:57:11 +0100 Subject: [PATCH 1128/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 6d2d20b05a..a361627eca 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -60,7 +60,7 @@ const Keccak = { * Implementation of [pre-NIST Keccak](https://keccak.team/keccak.html) hash function. * Supports output lengths of 256, 384, or 512 bits. * - * Pre-NIST SHA-3 is a variant of the Keccak hash function, which was standardized by NIST in 2015. + * Keccak won the SHA-3 competition and was slightly altered before being standardized as SHA-3 by NIST in 2015. * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. * * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. From 489db6b455ecd5104f035515fb12d5fc142afbe8 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 13 Dec 2023 15:57:19 +0100 Subject: [PATCH 1129/1215] Update src/lib/keccak.ts Co-authored-by: jackryanservia <90076280+jackryanservia@users.noreply.github.com> --- src/lib/keccak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index a361627eca..d43ee8fc28 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -65,7 +65,7 @@ const Keccak = { * * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. * - * {@link Keccak.preNist} accepts a list of byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. + * {@link Keccak.preNist} accepts a list of big-endian byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. * * The hash output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. * From ca1c02e7295f7eefc31790812fed26707f96724f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 15:59:23 +0100 Subject: [PATCH 1130/1215] add verbose argument to nonprovable equivalence test --- src/lib/testing/equivalent.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 2e8384d7da..7a0f20fd7d 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -122,7 +122,7 @@ function toUnion(spec: OrUnion): FromSpecUnion { function equivalent< In extends Tuple>, Out extends ToSpec ->({ from, to }: { from: In; to: Out }) { +>({ from, to, verbose }: { from: In; to: Out; verbose?: boolean }) { return function run( f1: (...args: Params1) => First, f2: (...args: Params2) => Second, @@ -130,7 +130,8 @@ function equivalent< ) { let generators = from.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; - test(...(generators as any[]), (...args) => { + let start = performance.now(); + let nRuns = test(...(generators as any[]), (...args) => { args.pop(); let inputs = args as Params1; handleErrors( @@ -143,6 +144,14 @@ function equivalent< label ); }); + + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log( + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` + ); + } }; } From 0fbc626ff9a284b51099368bc70fc89f8d465fc9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 16:00:01 +0100 Subject: [PATCH 1131/1215] run keccak unit tests outside circuit for now --- src/lib/keccak.unit-test.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 454874cdcb..a811b018d9 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,11 +1,7 @@ import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; import { Random, sample } from './testing/random.js'; -import { - equivalentAsync, - equivalentProvable, - spec, -} from './testing/equivalent.js'; +import { equivalent, equivalentAsync, spec } from './testing/equivalent.js'; import { keccak_224, keccak_256, @@ -37,7 +33,8 @@ const testImplementations = { const lengths = [256, 384, 512] as const; -// witness construction checks +// checks outside circuit +// TODO: fix witness generation slowness const bytes = (length: number) => { const Bytes_ = Bytes(length); @@ -55,13 +52,13 @@ for (let length of lengths) { let inputBytes = bytes(preimageLength); let outputBytes = bytes(length / 8); - equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( testImplementations.sha3[length], (x) => Keccak.nistSha3(length, x), `sha3 ${length}` ); - equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( + equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( testImplementations.preNist[length], (x) => Keccak.preNist(length, x), `keccak ${length}` @@ -74,12 +71,11 @@ const digestLength = lengths[Math.floor(Math.random() * 3)]; // Digest length in bytes const digestLengthBytes = digestLength / 8; -// Chose a random preimage length -const preImageLength = Math.floor(digestLength / (Math.random() * 4 + 2)); +const preImageLength = 32; // No need to test Ethereum because it's just a special case of preNist const KeccakProgram = ZkProgram({ - name: 'keccak-test', + name: `keccak-test-${digestLength}`, publicInput: Bytes(preImageLength).provable, publicOutput: Bytes(digestLengthBytes).provable, methods: { From c3fa6cee0771aec959b993c6e8d0d533f0c7688c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 16:24:24 +0100 Subject: [PATCH 1132/1215] reduce witnessing time in xor by calling exists fewer times --- src/lib/gadgets/bitwise.ts | 105 ++++++++++++++++++------------------- src/lib/gadgets/common.ts | 5 -- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 4c930de649..21ed00af11 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -6,9 +6,10 @@ import { MAX_BITS, assert, witnessSlice, - witnessNextValue, divideWithRemainder, toVar, + exists, + bitSlice, } from './common.js'; import { rangeCheck64 } from './range-check.js'; @@ -81,66 +82,71 @@ function xor(a: Field, b: Field, length: number) { } // builds a xor chain -function buildXor( - a: Field, - b: Field, - expectedOutput: Field, - padLength: number -) { +function buildXor(a: Field, b: Field, out: Field, padLength: number) { // construct the chain of XORs until padLength is 0 while (padLength !== 0) { // slices the inputs into 4x 4bit-sized chunks - // slices of a - let in1_0 = witnessSlice(a, 0, 4); - let in1_1 = witnessSlice(a, 4, 4); - let in1_2 = witnessSlice(a, 8, 4); - let in1_3 = witnessSlice(a, 12, 4); - - // slices of b - let in2_0 = witnessSlice(b, 0, 4); - let in2_1 = witnessSlice(b, 4, 4); - let in2_2 = witnessSlice(b, 8, 4); - let in2_3 = witnessSlice(b, 12, 4); - - // slices of expected output - let out0 = witnessSlice(expectedOutput, 0, 4); - let out1 = witnessSlice(expectedOutput, 4, 4); - let out2 = witnessSlice(expectedOutput, 8, 4); - let out3 = witnessSlice(expectedOutput, 12, 4); + let slices = exists(15, () => { + let a0 = a.toBigInt(); + let b0 = b.toBigInt(); + let out0 = out.toBigInt(); + return [ + // slices of a + bitSlice(a0, 0, 4), + bitSlice(a0, 4, 4), + bitSlice(a0, 8, 4), + bitSlice(a0, 12, 4), + + // slices of b + bitSlice(b0, 0, 4), + bitSlice(b0, 4, 4), + bitSlice(b0, 8, 4), + bitSlice(b0, 12, 4), + + // slices of expected output + bitSlice(out0, 0, 4), + bitSlice(out0, 4, 4), + bitSlice(out0, 8, 4), + bitSlice(out0, 12, 4), + + // next values + a0 >> 16n, + b0 >> 16n, + out0 >> 16n, + ]; + }); + + // prettier-ignore + let [ + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3, + aNext, bNext, outNext + ] = slices; // assert that the xor of the slices is correct, 16 bit at a time + // prettier-ignore Gates.xor( - a, - b, - expectedOutput, - in1_0, - in1_1, - in1_2, - in1_3, - in2_0, - in2_1, - in2_2, - in2_3, - out0, - out1, - out2, - out3 + a, b, out, + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3 ); // update the values for the next loop iteration - a = witnessNextValue(a); - b = witnessNextValue(b); - expectedOutput = witnessNextValue(expectedOutput); + a = aNext; + b = bNext; + out = outNext; padLength = padLength - 16; } // inputs are zero and length is zero, add the zero check - we reached the end of our chain - Gates.zero(a, b, expectedOutput); + Gates.zero(a, b, out); let zero = new Field(0); zero.assertEquals(a); zero.assertEquals(b); - zero.assertEquals(expectedOutput); + zero.assertEquals(out); } function and(a: Field, b: Field, length: number) { @@ -160,15 +166,8 @@ function and(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert( - a.toBigInt() < max, - `${a.toBigInt()} does not fit into ${padLength} bits` - ); - - assert( - b.toBigInt() < max, - `${b.toBigInt()} does not fit into ${padLength} bits` - ); + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); return new Field(a.toBigInt() & b.toBigInt()); } diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index e6e6c873cc..7d2057972f 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -16,7 +16,6 @@ export { assert, bitSlice, witnessSlice, - witnessNextValue, divideWithRemainder, }; @@ -84,10 +83,6 @@ function witnessSlice(f: Field, start: number, length: number) { }); } -function witnessNextValue(current: Field) { - return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); -} - function divideWithRemainder(numerator: bigint, denominator: bigint) { const quotient = numerator / denominator; const remainder = numerator - denominator * quotient; From cab7839893efc24fae784defdffce19022808b6c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 16:38:51 +0100 Subject: [PATCH 1133/1215] pad witnesses in raw gate to avoid try-catch in plonk_constraint_system --- src/lib/gates.ts | 31 ++++++++++++++++++++++++++++--- src/snarky.d.ts | 23 +---------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 5d620066c5..a8900cfe49 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,5 +1,6 @@ -import { KimchiGateType, Snarky } from '../snarky.js'; +import { Snarky } from '../snarky.js'; import { FieldConst, type Field } from './field.js'; +import { exists } from './gadgets/common.js'; import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; @@ -13,6 +14,7 @@ export { generic, foreignFieldAdd, foreignFieldMul, + KimchiGateType, }; const Gates = { @@ -150,7 +152,7 @@ function generic( } function zero(a: Field, b: Field, c: Field) { - Snarky.gates.zero(a.value, b.value, c.value); + raw(KimchiGateType.Zero, [a, b, c], []); } /** @@ -234,9 +236,32 @@ function foreignFieldMul(inputs: { } function raw(kind: KimchiGateType, values: Field[], coefficients: bigint[]) { + let n = values.length; + let padding = exists(15 - n, () => Array(15 - n).fill(0n)); Snarky.gates.raw( kind, - MlArray.to(values.map((x) => x.value)), + MlArray.to(values.concat(padding).map((x) => x.value)), MlArray.to(coefficients.map(FieldConst.fromBigint)) ); } + +enum KimchiGateType { + Zero, + Generic, + Poseidon, + CompleteAdd, + VarBaseMul, + EndoMul, + EndoMulScalar, + Lookup, + CairoClaim, + CairoInstruction, + CairoFlags, + CairoTransition, + RangeCheck0, + RangeCheck1, + ForeignFieldAdd, + ForeignFieldMul, + Xor16, + Rot64, +} diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 871fbeecaf..2c47b9dc63 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -25,6 +25,7 @@ import type { WasmFpSrs, WasmFqSrs, } from './bindings/compiled/node_bindings/plonk_wasm.cjs'; +import type { KimchiGateType } from './lib/gates.ts'; export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType, getWasm }; @@ -33,7 +34,6 @@ export { Snarky, Test, JsonGate, - KimchiGateType, MlPublicKey, MlPublicKeyVar, FeatureFlags, @@ -542,27 +542,6 @@ declare const Snarky: { }; }; -declare enum KimchiGateType { - Zero, - Generic, - Poseidon, - CompleteAdd, - VarBaseMul, - EndoMul, - EndoMulScalar, - Lookup, - CairoClaim, - CairoInstruction, - CairoFlags, - CairoTransition, - RangeCheck0, - RangeCheck1, - ForeignFieldAdd, - ForeignFieldMul, - Xor16, - Rot64, -} - type GateType = | 'Zero' | 'Generic' From d3dec8d8c82dcc92aeb4951317f0e898089de3b2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 16:44:20 +0100 Subject: [PATCH 1134/1215] remove witness slice altogether --- src/lib/gadgets/bitwise.ts | 40 ++++++++++++++++++++++---------------- src/lib/gadgets/common.ts | 11 ----------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 21ed00af11..f4bb9551db 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,7 +5,6 @@ import { Gates } from '../gates.js'; import { MAX_BITS, assert, - witnessSlice, divideWithRemainder, toVar, exists, @@ -243,27 +242,34 @@ function rot( // TODO this is an abstraction leak, but not clear to me how to improve toVar(0n); + // slice the bound into chunks + let boundSlices = exists(12, () => { + let bound0 = bound.toBigInt(); + return [ + bitSlice(bound0, 52, 12), // bits 52-64 + bitSlice(bound0, 40, 12), // bits 40-52 + bitSlice(bound0, 28, 12), // bits 28-40 + bitSlice(bound0, 16, 12), // bits 16-28 + + bitSlice(bound0, 14, 2), // bits 14-16 + bitSlice(bound0, 12, 2), // bits 12-14 + bitSlice(bound0, 10, 2), // bits 10-12 + bitSlice(bound0, 8, 2), // bits 8-10 + bitSlice(bound0, 6, 2), // bits 6-8 + bitSlice(bound0, 4, 2), // bits 4-6 + bitSlice(bound0, 2, 2), // bits 2-4 + bitSlice(bound0, 0, 2), // bits 0-2 + ]; + }); + let [b52, b40, b28, b16, b14, b12, b10, b8, b6, b4, b2, b0] = boundSlices; + // Compute current row Gates.rotate( field, rotated, excess, - [ - witnessSlice(bound, 52, 12), // bits 52-64 - witnessSlice(bound, 40, 12), // bits 40-52 - witnessSlice(bound, 28, 12), // bits 28-40 - witnessSlice(bound, 16, 12), // bits 16-28 - ], - [ - witnessSlice(bound, 14, 2), // bits 14-16 - witnessSlice(bound, 12, 2), // bits 12-14 - witnessSlice(bound, 10, 2), // bits 10-12 - witnessSlice(bound, 8, 2), // bits 8-10 - witnessSlice(bound, 6, 2), // bits 6-8 - witnessSlice(bound, 4, 2), // bits 4-6 - witnessSlice(bound, 2, 2), // bits 2-4 - witnessSlice(bound, 0, 2), // bits 0-2 - ], + [b52, b40, b28, b16], + [b14, b12, b10, b8, b6, b4, b2, b0], big2PowerRot ); // Compute next row diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 7d2057972f..9da4490723 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,4 +1,3 @@ -import { Provable } from '../provable.js'; import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; @@ -15,7 +14,6 @@ export { isVar, assert, bitSlice, - witnessSlice, divideWithRemainder, }; @@ -74,15 +72,6 @@ function bitSlice(x: bigint, start: number, length: number) { return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n); } -function witnessSlice(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - - return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); - }); -} - function divideWithRemainder(numerator: bigint, denominator: bigint) { const quotient = numerator / denominator; const remainder = numerator - denominator * quotient; From e5810f48625d1702f738e3d5a0bb8843e6cf0fcc Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 17:01:39 +0100 Subject: [PATCH 1135/1215] make old unit tests work --- src/index.ts | 14 ++------ src/lib/hashes-combined.ts | 41 ++++++++++++++++++++++ src/lib/keccak-old.unit-test.ts | 44 ++++++++++-------------- src/lib/provable-types/bytes.ts | 4 +-- src/lib/provable-types/provable-types.ts | 1 + 5 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 src/lib/hashes-combined.ts diff --git a/src/index.ts b/src/index.ts index 3f29a8059f..4a440bf143 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,17 +9,9 @@ export { } from './lib/foreign-field.js'; export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; -export { TokenSymbol } from './lib/hash.js'; - -import { Poseidon } from './lib/hash.js'; -import { Keccak } from './lib/keccak.js'; - -const Hash = { - hash: Poseidon.hash, - Poseidon, - Keccak, -}; -export { Poseidon, Keccak, Hash }; +export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Keccak } from './lib/keccak.js'; +export { Hash } from './lib/hashes-combined.js'; export * from './lib/signature.js'; export type { diff --git a/src/lib/hashes-combined.ts b/src/lib/hashes-combined.ts new file mode 100644 index 0000000000..5608627e43 --- /dev/null +++ b/src/lib/hashes-combined.ts @@ -0,0 +1,41 @@ +import { Poseidon } from './hash.js'; +import { Keccak } from './keccak.js'; +import { Bytes } from './provable-types/provable-types.js'; + +export { Hash }; + +// TODO do we want this? +const Hash = { + hash: Poseidon.hash, + Poseidon, + SHA3_256: { + hash(bytes: Bytes) { + return Keccak.nistSha3(256, bytes); + }, + }, + SHA3_384: { + hash(bytes: Bytes) { + return Keccak.nistSha3(384, bytes); + }, + }, + SHA3_512: { + hash(bytes: Bytes) { + return Keccak.nistSha3(512, bytes); + }, + }, + Keccak256: { + hash(bytes: Bytes) { + return Keccak.preNist(256, bytes); + }, + }, + Keccak384: { + hash(bytes: Bytes) { + return Keccak.preNist(384, bytes); + }, + }, + Keccak512: { + hash(bytes: Bytes) { + return Keccak.preNist(512, bytes); + }, + }, +}; diff --git a/src/lib/keccak-old.unit-test.ts b/src/lib/keccak-old.unit-test.ts index c934bcf15e..a39179f60a 100644 --- a/src/lib/keccak-old.unit-test.ts +++ b/src/lib/keccak-old.unit-test.ts @@ -1,9 +1,10 @@ import { test, Random } from './testing/property.js'; import { UInt8 } from './int.js'; -import { Hash } from './hash.js'; +import { Hash } from './hashes-combined.js'; import { Provable } from './provable.js'; import { expect } from 'expect'; import assert from 'assert'; +import { Bytes } from './provable-types/provable-types.js'; let RandomUInt8 = Random.map(Random.uint8, (x) => UInt8.from(x)); @@ -13,16 +14,14 @@ test(Random.uint8, Random.uint8, (x, y, assert) => { assert(z instanceof UInt8); assert(z.toBigInt() === x); assert(z.toString() === x.toString()); - assert(z.isConstant()); assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); - assert((z = new UInt8(z.value)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value.value)) instanceof UInt8 && z.toBigInt() === x); z = new UInt8(y); assert(z instanceof UInt8); assert(z.toString() === y.toString()); - assert(z.toJSON() === y.toString()); }); // handles all numbers up to 2^8 @@ -50,29 +49,27 @@ function checkHashInCircuit() { .create()() .map((x) => Provable.witness(UInt8, () => UInt8.from(x))); - checkHashConversions(data); + checkHashConversions(Bytes.from(data)); }); } -function checkHashConversions(data: UInt8[]) { +function checkHashConversions(data: Bytes) { Provable.asProver(() => { - expectDigestToEqualHex(Hash.SHA224.hash(data)); - expectDigestToEqualHex(Hash.SHA256.hash(data)); - expectDigestToEqualHex(Hash.SHA384.hash(data)); - expectDigestToEqualHex(Hash.SHA512.hash(data)); + expectDigestToEqualHex(Hash.SHA3_256.hash(data)); + expectDigestToEqualHex(Hash.SHA3_384.hash(data)); + expectDigestToEqualHex(Hash.SHA3_512.hash(data)); expectDigestToEqualHex(Hash.Keccak256.hash(data)); }); } -function expectDigestToEqualHex(digest: UInt8[]) { - const hex = UInt8.toHex(digest); - expect(equals(digest, UInt8.fromHex(hex))).toBe(true); +function expectDigestToEqualHex(digest: Bytes) { + const hex = digest.toHex(); + expect(digest).toEqual(Bytes.fromHex(hex)); } -function equals(a: UInt8[], b: UInt8[]): boolean { +function equals(a: Bytes, b: Bytes): boolean { if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); - + for (let i = 0; i < a.length; i++) a.bytes[i].assertEquals(b.bytes[i]); return true; } @@ -246,24 +243,21 @@ function testExpected({ Provable.runAndCheck(() => { assert(message.length % 2 === 0); - let fields = UInt8.fromHex(message); - let expectedHash = UInt8.fromHex(expected); + let fields = Bytes.fromHex(message); + let expectedHash = Bytes.fromHex(expected); Provable.asProver(() => { if (nist) { - let hashed; + let hashed: Bytes; switch (length) { - case 224: - hashed = Hash.SHA224.hash(fields); - break; case 256: - hashed = Hash.SHA256.hash(fields); + hashed = Hash.SHA3_256.hash(fields); break; case 384: - hashed = Hash.SHA384.hash(fields); + hashed = Hash.SHA3_384.hash(fields); break; case 512: - hashed = Hash.SHA512.hash(fields); + hashed = Hash.SHA3_512.hash(fields); break; default: assert(false); diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index 8c0b08b289..ddbe6873aa 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -73,8 +73,8 @@ class Bytes { /** * Convert {@link Bytes} to a hex string. */ - toHex(xs: Bytes): string { - return xs.bytes + toHex(): string { + return this.bytes .map((x) => x.toBigInt().toString(16).padStart(2, '0')) .join(''); } diff --git a/src/lib/provable-types/provable-types.ts b/src/lib/provable-types/provable-types.ts index 1d850aa3c5..cd4c2ed2f1 100644 --- a/src/lib/provable-types/provable-types.ts +++ b/src/lib/provable-types/provable-types.ts @@ -17,3 +17,4 @@ function Bytes(size: number) { return createBytes(size); } Bytes.from = InternalBytes.from; +Bytes.fromHex = InternalBytes.fromHex; From f96c1694ef170ef65bb76404f7b73ba41cfa4766 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 17:38:29 +0100 Subject: [PATCH 1136/1215] fix unit test flake --- src/lib/gadgets/foreign-field.unit-test.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index b4dc64f25f..135fca61a1 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -15,12 +15,14 @@ import { ZkProgram } from '../proof_system.js'; import { Provable } from '../provable.js'; import { assert } from './common.js'; import { + allConstant, and, constraintSystem, contains, equals, ifNotAllConstant, not, + or, repeat, withoutGenerics, } from '../testing/constraint-system.js'; @@ -326,10 +328,13 @@ constraintSystem( 'assert mul', from2, (x, y) => assertMulExample(x, y, F.modulus), - and( - contains([addChain(1), addChain(1), addChainedIntoMul]), - // assertMul() doesn't use any range checks besides on internal values and the quotient - containsNTimes(2, mrc) + or( + and( + contains([addChain(2), addChain(2), addChainedIntoMul]), + // assertMul() doesn't use any range checks besides on internal values and the quotient + containsNTimes(2, mrc) + ), + allConstant ) ); From 45b7b74bc26ffd54f63e78b0acd570dc51f122e0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 17:38:50 +0100 Subject: [PATCH 1137/1215] add run-debug script --- run | 2 +- run-debug | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100755 run-debug diff --git a/run b/run index b039136688..5011793494 100755 --- a/run +++ b/run @@ -1 +1 @@ -node --enable-source-maps --stack-trace-limit=1000 src/build/run.js $@ +node --enable-source-maps src/build/run.js $@ diff --git a/run-debug b/run-debug new file mode 100755 index 0000000000..05642ff1c3 --- /dev/null +++ b/run-debug @@ -0,0 +1 @@ +node --inspect-brk --enable-source-maps src/build/run.js $@ From 1cb8b0be3fe56941dd054d01752a66909fbc4b1c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 18:02:53 +0100 Subject: [PATCH 1138/1215] add toFields and expose Bytes --- src/index.ts | 1 + src/lib/provable-types/bytes.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/index.ts b/src/index.ts index 4a440bf143..b82033e1dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ export { export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; export { UInt32, UInt64, Int64, Sign, UInt8 } from './lib/int.js'; +export { Bytes } from './lib/provable-types/provable-types.js'; export { Gadgets } from './lib/gadgets/gadgets.js'; export { Types } from './bindings/mina-transaction/types.js'; diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index ddbe6873aa..992e10bd1f 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -50,6 +50,10 @@ class Bytes { return Uint8Array.from(this.bytes.map((x) => x.toNumber())); } + toFields() { + return this.bytes.map((x) => x.value); + } + /** * Create {@link Bytes} from a string. * From 4729c8bb3e7ab232aee84789fa4eff6ce67fa8e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 18:03:14 +0100 Subject: [PATCH 1139/1215] fix examples --- src/examples/crypto/ecdsa/ecdsa.ts | 47 ++++----------------- src/examples/crypto/ecdsa/run.ts | 4 +- src/examples/keccak.ts | 64 ----------------------------- src/examples/zkapps/hashing/hash.ts | 39 ++++++------------ src/examples/zkapps/hashing/run.ts | 23 ++--------- 5 files changed, 26 insertions(+), 151 deletions(-) delete mode 100644 src/examples/keccak.ts diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index cf2407df35..45639c41cb 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -4,37 +4,34 @@ import { createEcdsa, createForeignCurve, Bool, - Struct, - Provable, - Field, Keccak, - Gadgets, + Bytes, } from 'o1js'; -export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Message32 }; +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32 }; class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} class Scalar extends Secp256k1.Scalar {} class Ecdsa extends createEcdsa(Secp256k1) {} -class Message32 extends Message(32) {} +class Bytes32 extends Bytes(32) {} const keccakAndEcdsa = ZkProgram({ name: 'ecdsa', - publicInput: Message32, + publicInput: Bytes32.provable, publicOutput: Bool, methods: { verifyEcdsa: { privateInputs: [Ecdsa.provable, Secp256k1.provable], - method(message: Message32, signature: Ecdsa, publicKey: Secp256k1) { - return signature.verify(message.array, publicKey); + method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verify(message, publicKey); }, }, sha3: { privateInputs: [], - method(message: Message32) { - Keccak.nistSha3(256, message.array); + method(message: Bytes32) { + Keccak.nistSha3(256, message); return Bool(true); }, }, @@ -55,31 +52,3 @@ const ecdsa = ZkProgram({ }, }, }); - -// helper: class for a message of n bytes - -function Message(lengthInBytes: number) { - return class Message extends Struct({ - array: Provable.Array(Field, lengthInBytes), - }) { - static from(message: string | Uint8Array) { - if (typeof message === 'string') { - message = new TextEncoder().encode(message); - } - let padded = new Uint8Array(32); - padded.set(message); - return new this({ array: [...padded].map(Field) }); - } - - toBytes() { - return Uint8Array.from(this.array.map((f) => Number(f))); - } - - /** - * Important: check that inputs are, in fact, bytes - */ - static check(msg: { array: Field[] }) { - msg.array.forEach(Gadgets.rangeCheck8); - } - }; -} diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts index 377e18d50a..2a497de373 100644 --- a/src/examples/crypto/ecdsa/run.ts +++ b/src/examples/crypto/ecdsa/run.ts @@ -1,4 +1,4 @@ -import { Secp256k1, Ecdsa, keccakAndEcdsa, Message32, ecdsa } from './ecdsa.js'; +import { Secp256k1, Ecdsa, keccakAndEcdsa, ecdsa, Bytes32 } from './ecdsa.js'; import assert from 'assert'; // create an example ecdsa signature @@ -6,7 +6,7 @@ import assert from 'assert'; let privateKey = Secp256k1.Scalar.random(); let publicKey = Secp256k1.generator.scale(privateKey); -let message = Message32.from("what's up"); +let message = Bytes32.fromString("what's up"); let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); diff --git a/src/examples/keccak.ts b/src/examples/keccak.ts deleted file mode 100644 index 243b64996e..0000000000 --- a/src/examples/keccak.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Field, Provable, Hash, UInt8 } from 'snarkyjs'; - -function equals(a: UInt8[], b: UInt8[]): boolean { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) a[i].assertEquals(b[i]); - return true; -} - -function checkDigestHexConversion(digest: UInt8[]) { - console.log('Checking hex->digest, digest->hex matches'); - Provable.asProver(() => { - const hex = UInt8.toHex(digest); - const expected = UInt8.fromHex(hex); - if (equals(digest, expected)) { - console.log('✅ Digest matches'); - } else { - Provable.log(`hex: ${hex}\ndigest: ${digest}\nexpected:${expected}`); - console.log('❌ Digest does not match'); - } - }); -} - -console.log('Running SHA224 test'); -Provable.runAndCheck(() => { - let digest = Hash.SHA224.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - checkDigestHexConversion(digest); -}); - -console.log('Running SHA256 test'); -Provable.runAndCheck(() => { - let digest = Hash.SHA256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - checkDigestHexConversion(digest); -}); - -console.log('Running SHA384 test'); -Provable.runAndCheck(() => { - let digest = Hash.SHA384.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - checkDigestHexConversion(digest); -}); - -// TODO: This test fails -console.log('Running SHA512 test'); -Provable.runAndCheck(() => { - let digest = Hash.SHA512.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - checkDigestHexConversion(digest); -}); - -console.log('Running keccak hash test'); -Provable.runAndCheck(() => { - let digest = Hash.Keccak256.hash([new UInt8(1), new UInt8(2), new UInt8(3)]); - checkDigestHexConversion(digest); -}); - -console.log('Running Poseidon test'); -Provable.runAndCheck(() => { - let digest = Hash.Poseidon.hash([Field(1), Field(2), Field(3)]); - Provable.log(digest); -}); - -console.log('Running default hash test'); -Provable.runAndCheck(() => { - let digest = Hash.hash([Field(1), Field(2), Field(3)]); - Provable.log(digest); -}); diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts index e593afae35..9ad9947dfa 100644 --- a/src/examples/zkapps/hashing/hash.ts +++ b/src/examples/zkapps/hashing/hash.ts @@ -1,23 +1,16 @@ import { Hash, - UInt8, Field, SmartContract, state, State, method, Permissions, - Struct, - Provable, + Bytes, } from 'o1js'; let initialCommitment: Field = Field(0); -// 32 UInts -export class HashInput extends Struct({ - data: Provable.Array(UInt8, 32), -}) {} - export class HashStorage extends SmartContract { @state(Field) commitment = State(); @@ -30,33 +23,27 @@ export class HashStorage extends SmartContract { this.commitment.set(initialCommitment); } - @method SHA224(xs: HashInput) { - const shaHash = Hash.SHA224.hash(xs.data); - const commitment = Hash.hash(shaHash.map((f) => f.toField())); - this.commitment.set(commitment); - } - - @method SHA256(xs: HashInput) { - const shaHash = Hash.SHA256.hash(xs.data); - const commitment = Hash.hash(shaHash.map((f) => f.toField())); + @method SHA256(xs: Bytes) { + const shaHash = Hash.SHA3_256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method SHA384(xs: HashInput) { - const shaHash = Hash.SHA384.hash(xs.data); - const commitment = Hash.hash(shaHash.map((f) => f.toField())); + @method SHA384(xs: Bytes) { + const shaHash = Hash.SHA3_384.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method SHA512(xs: HashInput) { - const shaHash = Hash.SHA512.hash(xs.data); - const commitment = Hash.hash(shaHash.map((f) => f.toField())); + @method SHA512(xs: Bytes) { + const shaHash = Hash.SHA3_512.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method Keccak256(xs: HashInput) { - const shaHash = Hash.Keccak256.hash(xs.data); - const commitment = Hash.hash(shaHash.map((f) => f.toField())); + @method Keccak256(xs: Bytes) { + const shaHash = Hash.Keccak256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } } diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts index b413cd09db..9350211d68 100644 --- a/src/examples/zkapps/hashing/run.ts +++ b/src/examples/zkapps/hashing/run.ts @@ -1,9 +1,5 @@ -import { HashStorage, HashInput } from './hash.js'; -import { Mina, PrivateKey, AccountUpdate, UInt8 } from 'snarkyjs'; -import { getProfiler } from '../../profiler.js'; - -const HashProfier = getProfiler('Hash'); -HashProfier.start('Hash test flow'); +import { HashStorage } from './hash.js'; +import { Mina, PrivateKey, AccountUpdate, Bytes } from 'o1js'; let txn; let proofsEnabled = true; @@ -25,9 +21,7 @@ const zkAppAddress = zkAppPrivateKey.toPublicKey(); const zkAppInstance = new HashStorage(zkAppAddress); // 0, 1, 2, 3, ..., 32 -const hashData = new HashInput({ - data: Array.from({ length: 32 }, (_, i) => i).map((x) => UInt8.from(x)), -}); +const hashData = Bytes.from(Array.from({ length: 32 }, (_, i) => i)); console.log('Deploying Hash Example....'); txn = await Mina.transaction(feePayer.publicKey, () => { @@ -42,15 +36,6 @@ const initialState = let currentState; console.log('Initial State', initialState); -console.log(`Updating commitment from ${initialState} using SHA224 ...`); -txn = await Mina.transaction(feePayer.publicKey, () => { - zkAppInstance.SHA224(hashData); -}); -await txn.prove(); -await txn.sign([feePayer.privateKey]).send(); -currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); -console.log(`Current state successfully updated to ${currentState}`); - console.log(`Updating commitment from ${initialState} using SHA256 ...`); txn = await Mina.transaction(feePayer.publicKey, () => { zkAppInstance.SHA256(hashData); @@ -86,5 +71,3 @@ await txn.prove(); await txn.sign([feePayer.privateKey]).send(); currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); console.log(`Current state successfully updated to ${currentState}`); - -HashProfier.stop().store(); From 0769e6a8a32c39b2576bb4cfe0ca3aee9a9c1459 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 18:09:08 +0100 Subject: [PATCH 1140/1215] fix cs example --- .../vk-regression/plain-constraint-system.ts | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index 20bf05fa12..67176a9f75 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -1,4 +1,4 @@ -import { Field, Group, Gadgets, Provable, Scalar, Hash, UInt8 } from 'o1js'; +import { Field, Group, Gadgets, Provable, Scalar, Hash, Bytes } from 'o1js'; export { GroupCS, BitwiseCS, HashCS }; @@ -84,39 +84,27 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { }, }); -const HashCS = constraintSystem('Hashes', { - SHA224() { - let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => - Provable.witness(UInt8, () => UInt8.from(x)) - ); - Hash.SHA224.hash(xs); - }, +const Bytes32 = Bytes(32); +const bytes32 = Bytes32.from([]); +const HashCS = constraintSystem('Hashes', { SHA256() { - let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => - Provable.witness(UInt8, () => UInt8.from(x)) - ); - Hash.SHA256.hash(xs); + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_256.hash(xs); }, SHA384() { - let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => - Provable.witness(UInt8, () => UInt8.from(x)) - ); - Hash.SHA384.hash(xs); + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_384.hash(xs); }, SHA512() { - let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => - Provable.witness(UInt8, () => UInt8.from(x)) - ); - Hash.SHA512.hash(xs); + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_512.hash(xs); }, Keccak256() { - let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => - Provable.witness(UInt8, () => UInt8.from(x)) - ); + let xs = Provable.witness(Bytes32.provable, () => bytes32); Hash.Keccak256.hash(xs); }, From 231e87c10c32ef66c7b16735ce8e77f6a9c16321 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 18:09:32 +0100 Subject: [PATCH 1141/1215] vk regression --- tests/vk-regression/vk-regression.json | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 657960f923..1c3ae041bf 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -202,6 +202,35 @@ "hash": "" } }, + "Hashes": { + "digest": "Hashes", + "methods": { + "SHA256": { + "rows": 14494, + "digest": "949539824d56622702d9ac048e8111e9" + }, + "SHA384": { + "rows": 14541, + "digest": "93dedf5824cab797d48e7a98c53c6bf3" + }, + "SHA512": { + "rows": 14588, + "digest": "3756008585b30a3951ed6455a7fbcdb0" + }, + "Keccak256": { + "rows": 14493, + "digest": "1ab08bd64002a0dd0a82f74df445de05" + }, + "Poseidon": { + "rows": 208, + "digest": "afa1f9920f1f657ab015c02f9b2f6c52" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, "ecdsa-only": { "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", "methods": { From 6af37c4abc4a5aefa52c35abc28d8c59736482dd Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:29:43 -0800 Subject: [PATCH 1142/1215] chore(mina): update mina submodule to latest commit for up-to-date features and bug fixes --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index ae94ff0180..d4314b0920 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit ae94ff0180d9e4653cbfb32c527fa7ab22423f19 +Subproject commit d4314b0920fe06d7ba9f790fb803142da6883570 From 299244bbd65b1957b1fc2f220c806696860aa782 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:30:26 -0800 Subject: [PATCH 1143/1215] chore(bindings): update subproject commit hash to latest version for up-to-date dependencies and features --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 98eb83e34f..7ea6187ab6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 98eb83e34f2a7f99c3b5983b458dacf89211c93e +Subproject commit 7ea6187ab61508b913da741e385fdfc52661e224 From 3a81547fd712d6e7ee3a9666d1cd1132904d0545 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:31:28 -0800 Subject: [PATCH 1144/1215] chore(bindings): update bindings subproject to latest commit for up-to-date functionality --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7ea6187ab6..c111dca28e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7ea6187ab61508b913da741e385fdfc52661e224 +Subproject commit c111dca28ebcb548ded32a7a55c6c91e333b90c3 From 5c533a1fcef01df5759fe8bbb410884ef8fcf052 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:33:00 -0800 Subject: [PATCH 1145/1215] fix(README-dev.md): specify info for installing deps Co-authored-by: Gregor Mitscha-Baude --- README-dev.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-dev.md b/README-dev.md index 35f0bd2d88..b3a79abf44 100644 --- a/README-dev.md +++ b/README-dev.md @@ -13,8 +13,8 @@ Before starting, ensure you have the following tools installed: - [Git](https://git-scm.com/) - [Node.js and npm](https://nodejs.org/) -- [Dune](https://github.com/ocaml/dune) -- [Cargo](https://www.rust-lang.org/learn/get-started) +- [Dune](https://github.com/ocaml/dune) (only needed when compiling o1js from source) +- [Cargo](https://www.rust-lang.org/learn/get-started) (only needed when compiling o1js from source) After cloning the repository, you need to fetch the submodules: From 028e38de6d6ec5295bfc290d02586a57dc4619d5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:33:18 -0800 Subject: [PATCH 1146/1215] feat(README-dev.md): spelling mistake Co-authored-by: Gregor Mitscha-Baude --- README-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-dev.md b/README-dev.md index b3a79abf44..7ba9796c39 100644 --- a/README-dev.md +++ b/README-dev.md @@ -37,7 +37,7 @@ This will compile the TypeScript source files, making it ready for use. The comp If you need to regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js, so you can build them from within the o1js repo. -o1js depends on OCaml code that is transplied to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/o1-labs/snarkyhttps://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. +o1js depends on OCaml code that is transpiled to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/o1-labs/snarkyhttps://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. The compiled artifacts are stored under `src/bindings/compiled`, and are version-controlled to simplify the build process for end-users. From 0979c1032041029ff289d217df2970738f3a7465 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:33:30 -0800 Subject: [PATCH 1147/1215] Update README-dev.md Co-authored-by: Gregor Mitscha-Baude --- README-dev.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-dev.md b/README-dev.md index 7ba9796c39..ec6e00b673 100644 --- a/README-dev.md +++ b/README-dev.md @@ -67,10 +67,10 @@ To compile the wasm code, a combination of Cargo and Dune is used. Both build fi For the wasm build, the output files are: -- `plonk_wasm_bg.wasm.d.ts`: TypeScript definition files describing the types of .wasm or .js files. -- `plonk_wasm.d.ts`: TypeScript definition file for plonk_wasm.js. - `plonk_wasm_bg.wasm`: The compiled WebAssembly binary. +- `plonk_wasm_bg.wasm.d.ts`: TypeScript definition files describing the types of .wasm or .js files. - `plonk_wasm.js`: JavaScript file that wraps the WASM code for use in Node.js. +- `plonk_wasm.d.ts`: TypeScript definition file for plonk_wasm.js. ### Generated Constant Types From 12f0b2dd20029315311d01c4ac6b05f16e768d9c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 13 Dec 2023 18:42:19 +0100 Subject: [PATCH 1148/1215] add toInput --- src/lib/int.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/int.ts b/src/lib/int.ts index a4cc974c4c..84a38a4b53 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1291,6 +1291,10 @@ class UInt8 extends Struct({ Gadgets.rangeCheck8(x.value); } + static toInput(x: { value: Field }): HashInput { + return { packed: [[x.value, 8]] }; + } + /** * Turns a {@link UInt8} into a {@link UInt32}. */ From cb6190dfa76ef5f9937789105e5f584c9ecdea16 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 09:53:05 -0800 Subject: [PATCH 1149/1215] refactor(package.json): streamline script commands for better readability and maintainability feat(package.json): add new build commands for bindings, update-bindings, and wasm chore(src/bindings): update subproject commit hash for latest changes --- package.json | 7 +++---- src/bindings | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 283a7e21c2..adc279b9ec 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,10 @@ }, "scripts": { "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", - "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", - "wasm": "./src/bindings/scripts/update-wasm-and-types.sh", - "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", - "build:bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", + "build:bindings": "./src/bindings/scripts/build-snarkyjs-node.sh", + "build:update-bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", + "build:wasm": "./src/bindings/scripts/update-wasm-and-types.sh", "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", diff --git a/src/bindings b/src/bindings index c111dca28e..7cffd8b827 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c111dca28ebcb548ded32a7a55c6c91e333b90c3 +Subproject commit 7cffd8b827d6b7f217627d996367f615db4b6476 From e6fceba0b16e95824f89b553d821a60fcd5f72bd Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 10:32:35 -0800 Subject: [PATCH 1150/1215] chore(bindings): update subproject commit hash to 4b453f0caf2db1d0469b38a3478cc08c756610be for latest changes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7cffd8b827..4b453f0caf 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7cffd8b827d6b7f217627d996367f615db4b6476 +Subproject commit 4b453f0caf2db1d0469b38a3478cc08c756610be From aeeae041fa25ef56272f053bbe55779c0f9a1119 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Dec 2023 11:38:33 -0800 Subject: [PATCH 1151/1215] chore(bindings): update subproject commit hash to 225dd0ad25a8bf7469ba3b8dfbdb7ab72f5036c3 for latest changes --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 4b453f0caf..225dd0ad25 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4b453f0caf2db1d0469b38a3478cc08c756610be +Subproject commit 225dd0ad25a8bf7469ba3b8dfbdb7ab72f5036c3 From 7b305fbe5e09bf235034ba18b9983a393053bc47 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 11:35:44 +0100 Subject: [PATCH 1152/1215] support bytes from string without constructing type of specific length --- src/lib/provable-types/provable-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/provable-types/provable-types.ts b/src/lib/provable-types/provable-types.ts index cd4c2ed2f1..ad11e446b0 100644 --- a/src/lib/provable-types/provable-types.ts +++ b/src/lib/provable-types/provable-types.ts @@ -18,3 +18,4 @@ function Bytes(size: number) { } Bytes.from = InternalBytes.from; Bytes.fromHex = InternalBytes.fromHex; +Bytes.fromString = InternalBytes.fromString; From 9d444b9aec3f5556d7db837dab6b21a8f154dc63 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 11:35:59 +0100 Subject: [PATCH 1153/1215] adapt keccak doccomments to bytes input --- src/lib/keccak.ts | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index 76597dbe6d..ac63ceecb1 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -16,17 +16,17 @@ const Keccak = { * * Applies the SHA-3 hash function to a list of big-endian byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. * - * The function accepts a list of byte-sized {@link Field} elements as its input. However, the input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. * - * The output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. - * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. - * - * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * @param message - Big-endian {@link Bytes} representing the message to hash. * * ```ts - * let preimage = [5, 6, 19, 28, 19].map(Field); + * let preimage = Bytes.fromString("hello world"); * let digest256 = Keccak.nistSha3(256, preimage); * let digest384 = Keccak.nistSha3(384, preimage); * let digest512 = Keccak.nistSha3(512, preimage); @@ -42,17 +42,15 @@ const Keccak = { * * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. * - * The function expects an input as a list of big-endian byte-sized {@link Field} elements. However, the input should be range checked before calling this function, - * as this function does not perform internal range checking. This can be done using {@link Gadgets.rangeCheck8}. - * - * Produces an output which is a list of big-endian byte-sized {@link Field} elements and ensures output is within the specified range using {@link Gadgets.rangeCheck8}. + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. * - * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. + * Produces an output of {@link Bytes} of length 32. Both input and output bytes are big-endian. * - * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * @param message - Big-endian {@link Bytes} representing the message to hash. * * ```ts - * let preimage = [5, 6, 19, 28, 19].map(Field); + * let preimage = Bytes.fromString("hello world"); * let digest = Keccak.ethereum(preimage); * ``` */ @@ -68,17 +66,17 @@ const Keccak = { * * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. * - * {@link Keccak.preNist} accepts a list of big-endian byte-sized {@link Field} elements as its input. However, input values should be range-checked externally before being passed to this function. This can be done using {@link Gadgets.rangeCheck8}. + * {@link Keccak.preNist} accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. * - * The hash output is ensured to conform to the chosen bit length and is a list of big-endian byte-sized {@link Field} elements, range-checked using {@link Gadgets.rangeCheck8}. + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. * * @param len - Desired output length in bits. Valid options: 256, 384, 512. - * @param message - Big-endian list of byte-sized {@link Field} elements representing the message to hash. - * - * _Note:_ This function does not perform internal range checking on the input, this can be done by using {@link Gadgets.rangeCheck8}. + * @param message - Big-endian {@link Bytes} representing the message to hash. * * ```ts - * let preimage = [5, 6, 19, 28, 19].map(Field); + * let preimage = Bytes.fromString("hello world"); * let digest256 = Keccak.preNist(256, preimage); * let digest384 = Keccak.preNist(384, preimage); * let digest512= Keccak.preNist(512, preimage); From 3be3ac59d20f3746a48e3ddb7dc994bc78fd4d45 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 12:04:12 +0100 Subject: [PATCH 1154/1215] Hash doccomments --- src/lib/hashes-combined.ts | 88 +++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/lib/hashes-combined.ts b/src/lib/hashes-combined.ts index 5608627e43..8440a25b32 100644 --- a/src/lib/hashes-combined.ts +++ b/src/lib/hashes-combined.ts @@ -4,36 +4,122 @@ import { Bytes } from './provable-types/provable-types.js'; export { Hash }; -// TODO do we want this? +/** + * A collection of hash functions which can be used in provable code. + */ const Hash = { + /** + * Hashes the given field elements using [Poseidon](https://eprint.iacr.org/2019/458.pdf). Alias for `Poseidon.hash()`. + * + * ```ts + * let hash = Hash.hash([a, b, c]); + * ``` + * + * **Important:** This is by far the most efficient hash function o1js has available in provable code. + * Use it by default, if no compatibility concerns require you to use a different hash function. + * + * The Poseidon implementation operates over the native [Pallas base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) + * and uses parameters generated specifically for the [Mina](https://minaprotocol.com) blockchain. + * + * We use a `rate` of 2, which means that 2 field elements are hashed per permutation. + * A permutation causes 11 rows in the constraint system. + * + * You can find the full set of Poseidon parameters [here](https://github.com/o1-labs/o1js-bindings/blob/main/crypto/constants.ts). + */ hash: Poseidon.hash, + + /** + * The [Poseidon](https://eprint.iacr.org/2019/458.pdf) hash function. + * + * See {@link Hash.hash} for details and usage examples. + */ Poseidon, + + /** + * The SHA3 hash function with an output length of 256 bits. + */ SHA3_256: { + /** + * Hashes the given bytes using SHA3-256. + * + * This is an alias for `Keccak.nistSha3(256, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.nistSha3(256, bytes); }, }, + + /** + * The SHA3 hash function with an output length of 384 bits. + */ SHA3_384: { + /** + * Hashes the given bytes using SHA3-384. + * + * This is an alias for `Keccak.nistSha3(384, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.nistSha3(384, bytes); }, }, + + /** + * The SHA3 hash function with an output length of 512 bits. + */ SHA3_512: { + /** + * Hashes the given bytes using SHA3-512. + * + * This is an alias for `Keccak.nistSha3(512, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.nistSha3(512, bytes); }, }, + + /** + * The pre-NIST Keccak hash function with an output length of 256 bits. + */ Keccak256: { + /** + * Hashes the given bytes using Keccak-256. + * + * This is an alias for `Keccak.preNist(256, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.preNist(256, bytes); }, }, + + /** + * The pre-NIST Keccak hash function with an output length of 384 bits. + */ Keccak384: { + /** + * Hashes the given bytes using Keccak-384. + * + * This is an alias for `Keccak.preNist(384, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.preNist(384, bytes); }, }, + + /** + * The pre-NIST Keccak hash function with an output length of 512 bits. + */ Keccak512: { + /** + * Hashes the given bytes using Keccak-512. + * + * This is an alias for `Keccak.preNist(512, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ hash(bytes: Bytes) { return Keccak.preNist(512, bytes); }, From 4ad851e79143175c85c46d9c8057a21c4d65f085 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 12:14:42 +0100 Subject: [PATCH 1155/1215] don't double-constrain uint8 results --- src/lib/int.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 84a38a4b53..4daf746f42 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -984,6 +984,12 @@ class UInt8 extends Struct({ UInt8.checkConstant(this.value); } + static Unsafe = { + fromField(x: Field) { + return new UInt8(x.value); + }, + }; + /** * Add a {@link UInt8} to another {@link UInt8} without allowing overflow. * @@ -999,7 +1005,7 @@ class UInt8 extends Struct({ add(y: UInt8 | bigint | number) { let z = this.value.add(UInt8.from(y).value); Gadgets.rangeCheck8(z); - return UInt8.from(z); + return UInt8.Unsafe.fromField(z); } /** @@ -1017,7 +1023,7 @@ class UInt8 extends Struct({ sub(y: UInt8 | bigint | number) { let z = this.value.sub(UInt8.from(y).value); Gadgets.rangeCheck8(z); - return UInt8.from(z); + return UInt8.Unsafe.fromField(z); } /** @@ -1035,7 +1041,7 @@ class UInt8 extends Struct({ mul(y: UInt8 | bigint | number) { let z = this.value.mul(UInt8.from(y).value); Gadgets.rangeCheck8(z); - return UInt8.from(z); + return UInt8.Unsafe.fromField(z); } /** @@ -1097,8 +1103,8 @@ class UInt8 extends Struct({ Gadgets.rangeCheck16(q); Gadgets.rangeCheck16(r); - let remainder = UInt8.from(r); - let quotient = UInt8.from(q); + let remainder = UInt8.Unsafe.fromField(r); + let quotient = UInt8.Unsafe.fromField(q); remainder.assertLessThan(y); return { quotient, remainder }; @@ -1331,8 +1337,7 @@ class UInt8 extends Struct({ } private static checkConstant(x: Field) { - if (!x.isConstant()) return x.value; + if (!x.isConstant()) return; Gadgets.rangeCheck8(x); - return x.value; } } From 7ac389a9dff77a20a7041a5841ae0562e7464314 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 12:20:49 +0100 Subject: [PATCH 1156/1215] improve uint8 docs --- src/lib/int.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 4daf746f42..13c2553101 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -973,18 +973,24 @@ class UInt8 extends Struct({ static NUM_BITS = 8; /** - * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a {@link UInt8}. + * Create a {@link UInt8} from a bigint or number. * The max value of a {@link UInt8} is `2^8 - 1 = 255`. * * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. */ - constructor(x: number | bigint | string | FieldVar | UInt8) { + constructor(x: number | bigint | FieldVar | UInt8) { if (x instanceof UInt8) x = x.value.value; super({ value: Field(x) }); UInt8.checkConstant(this.value); } static Unsafe = { + /** + * Create a {@link UInt8} from a {@link Field} without constraining its range. + * + * **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 8 bits.\ + * Only use this if you know what you are doing, otherwise use the safe {@link UInt8.from}. + */ fromField(x: Field) { return new UInt8(x.value); }, From accc779542c3c7211bee63fed77730ddbb192595 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 12:30:11 +0100 Subject: [PATCH 1157/1215] fix int test --- src/lib/int.test.ts | 355 +++++++++++++++++++------------------------- 1 file changed, 154 insertions(+), 201 deletions(-) diff --git a/src/lib/int.test.ts b/src/lib/int.test.ts index 7fd38e496e..1914a9fede 100644 --- a/src/lib/int.test.ts +++ b/src/lib/int.test.ts @@ -2146,9 +2146,9 @@ describe('int', () => { it('1+1=2', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.add(y).assertEquals(new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.add(y).assertEquals(2); }); }).not.toThrow(); }); @@ -2156,15 +2156,15 @@ describe('int', () => { it('100+100=200', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(100))); - x.add(y).assertEquals(new UInt8(Field(200))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.add(y).assertEquals(new UInt8(200)); }); }).not.toThrow(); }); it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const n = Field((((1n << 8n) - 2n) / 2n).toString()); + const n = ((1n << 8n) - 2n) / 2n; expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => new UInt8(n)); @@ -2178,7 +2178,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.add(y); }); }).toThrow(); @@ -2189,9 +2189,9 @@ describe('int', () => { it('1-1=0', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.sub(y).assertEquals(new UInt8(Field(0))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.sub(y).assertEquals(new UInt8(0)); }); }).not.toThrow(); }); @@ -2199,9 +2199,9 @@ describe('int', () => { it('100-50=50', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(50))); - x.sub(y).assertEquals(new UInt8(Field(50))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(50)); + x.sub(y).assertEquals(new UInt8(50)); }); }).not.toThrow(); }); @@ -2209,8 +2209,8 @@ describe('int', () => { it('should throw on sub if results in negative number', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(0))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.sub(y); }); }).toThrow(); @@ -2221,9 +2221,9 @@ describe('int', () => { it('1x2=2', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(2))); - x.mul(y).assertEquals(new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.mul(y).assertEquals(new UInt8(2)); }); }).not.toThrow(); }); @@ -2231,9 +2231,9 @@ describe('int', () => { it('1x0=0', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(0))); - x.mul(y).assertEquals(new UInt8(Field(0))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mul(y).assertEquals(new UInt8(0)); }); }).not.toThrow(); }); @@ -2241,9 +2241,9 @@ describe('int', () => { it('12x20=240', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(12))); - const y = Provable.witness(UInt8, () => new UInt8(Field(20))); - x.mul(y).assertEquals(new UInt8(Field(240))); + const x = Provable.witness(UInt8, () => new UInt8(12)); + const y = Provable.witness(UInt8, () => new UInt8(20)); + x.mul(y).assertEquals(new UInt8(240)); }); }).not.toThrow(); }); @@ -2252,7 +2252,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.mul(y).assertEquals(UInt8.MAXINT()); }); }).not.toThrow(); @@ -2262,7 +2262,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + const y = Provable.witness(UInt8, () => new UInt8(2)); x.mul(y); }); }).toThrow(); @@ -2273,9 +2273,9 @@ describe('int', () => { it('2/1=2', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(2))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.div(y).assertEquals(new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(2)); }); }).not.toThrow(); }); @@ -2283,9 +2283,9 @@ describe('int', () => { it('0/1=0', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(0))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.div(y).assertEquals(new UInt8(Field(0))); + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(0)); }); }).not.toThrow(); }); @@ -2293,9 +2293,9 @@ describe('int', () => { it('20/10=2', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(20))); - const y = Provable.witness(UInt8, () => new UInt8(Field(10))); - x.div(y).assertEquals(new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(20)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.div(y).assertEquals(new UInt8(2)); }); }).not.toThrow(); }); @@ -2304,7 +2304,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.div(y).assertEquals(UInt8.MAXINT()); }); }).not.toThrow(); @@ -2314,7 +2314,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(0))); + const y = Provable.witness(UInt8, () => new UInt8(0)); x.div(y); }); }).toThrow(); @@ -2325,9 +2325,9 @@ describe('int', () => { it('1%1=0', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.mod(y).assertEquals(new UInt8(Field(0))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.mod(y).assertEquals(new UInt8(0)); }); }).not.toThrow(); }); @@ -2335,9 +2335,9 @@ describe('int', () => { it('50%32=18', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(50))); - const y = Provable.witness(UInt8, () => new UInt8(Field(32))); - x.mod(y).assertEquals(new UInt8(Field(18))); + const x = Provable.witness(UInt8, () => new UInt8(50)); + const y = Provable.witness(UInt8, () => new UInt8(32)); + x.mod(y).assertEquals(new UInt8(18)); }); }).not.toThrow(); }); @@ -2346,8 +2346,8 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(7))); - x.mod(y).assertEquals(new UInt8(Field(3))); + const y = Provable.witness(UInt8, () => new UInt8(7)); + x.mod(y).assertEquals(new UInt8(3)); }); }).not.toThrow(); }); @@ -2356,8 +2356,8 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.MAXINT()); - const y = Provable.witness(UInt8, () => new UInt8(Field(0))); - x.mod(y).assertEquals(new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mod(y).assertEquals(new UInt8(1)); }); }).toThrow(); }); @@ -2367,8 +2367,8 @@ describe('int', () => { it('1<2=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); x.assertLessThan(y); }); }).not.toThrow(); @@ -2377,8 +2377,8 @@ describe('int', () => { it('1<1=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertLessThan(y); }); }).toThrow(); @@ -2387,8 +2387,8 @@ describe('int', () => { it('2<1=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(2))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertLessThan(y); }); }).toThrow(); @@ -2397,8 +2397,8 @@ describe('int', () => { it('10<100=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(10))); - const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); x.assertLessThan(y); }); }).not.toThrow(); @@ -2407,8 +2407,8 @@ describe('int', () => { it('100<10=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); x.assertLessThan(y); }); }).toThrow(); @@ -2429,8 +2429,8 @@ describe('int', () => { it('1<=1=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertLessThanOrEqual(y); }); }).not.toThrow(); @@ -2439,8 +2439,8 @@ describe('int', () => { it('2<=1=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(2))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertLessThanOrEqual(y); }); }).toThrow(); @@ -2449,8 +2449,8 @@ describe('int', () => { it('10<=100=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(10))); - const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); x.assertLessThanOrEqual(y); }); }).not.toThrow(); @@ -2459,8 +2459,8 @@ describe('int', () => { it('100<=10=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); x.assertLessThanOrEqual(y); }); }).toThrow(); @@ -2481,8 +2481,8 @@ describe('int', () => { it('2>1=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(2))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertGreaterThan(y); }); }).not.toThrow(); @@ -2491,8 +2491,8 @@ describe('int', () => { it('1>1=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertGreaterThan(y); }); }).toThrow(); @@ -2501,8 +2501,8 @@ describe('int', () => { it('1>2=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); x.assertGreaterThan(y); }); }).toThrow(); @@ -2511,8 +2511,8 @@ describe('int', () => { it('100>10=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); x.assertGreaterThan(y); }); }).not.toThrow(); @@ -2521,8 +2521,8 @@ describe('int', () => { it('10>100=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1000))); - const y = Provable.witness(UInt8, () => new UInt8(Field(100000))); + const x = Provable.witness(UInt8, () => new UInt8(1000)); + const y = Provable.witness(UInt8, () => new UInt8(100000)); x.assertGreaterThan(y); }); }).toThrow(); @@ -2543,8 +2543,8 @@ describe('int', () => { it('1<=1=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertGreaterThanOrEqual(y); }); }).not.toThrow(); @@ -2553,8 +2553,8 @@ describe('int', () => { it('1>=2=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(1))); - const y = Provable.witness(UInt8, () => new UInt8(Field(2))); + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); x.assertGreaterThanOrEqual(y); }); }).toThrow(); @@ -2563,8 +2563,8 @@ describe('int', () => { it('100>=10=true', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(100))); - const y = Provable.witness(UInt8, () => new UInt8(Field(10))); + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); x.assertGreaterThanOrEqual(y); }); }).not.toThrow(); @@ -2573,8 +2573,8 @@ describe('int', () => { it('10>=100=false', () => { expect(() => { Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => new UInt8(Field(10))); - const y = Provable.witness(UInt8, () => new UInt8(Field(100))); + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); x.assertGreaterThanOrEqual(y); }); }).toThrow(); @@ -2597,18 +2597,7 @@ describe('int', () => { expect(() => { Provable.runAndCheck(() => { const x = Provable.witness(UInt8, () => UInt8.from(1)); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); - x.assertEquals(y); - }); - }).not.toThrow(); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - expect(() => { - Provable.runAndCheck(() => { - const x = Provable.witness(UInt8, () => UInt8.from('1')); - const y = Provable.witness(UInt8, () => new UInt8(Field(1))); + const y = Provable.witness(UInt8, () => new UInt8(1)); x.assertEquals(y); }); }).not.toThrow(); @@ -2620,20 +2609,17 @@ describe('int', () => { describe('Outside of circuit', () => { describe('add', () => { it('1+1=2', () => { - expect(new UInt8(Field(1)).add(1).toString()).toEqual('2'); + expect(new UInt8(1).add(1).toString()).toEqual('2'); }); it('50+50=100', () => { - expect(new UInt8(Field(50)).add(50).toString()).toEqual('100'); + expect(new UInt8(50).add(50).toString()).toEqual('100'); }); it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { - const value = Field((((1n << 8n) - 2n) / 2n).toString()); + const value = ((1n << 8n) - 2n) / 2n; expect( - new UInt8(value) - .add(new UInt8(value)) - .add(new UInt8(Field(1))) - .toString() + new UInt8(value).add(new UInt8(value)).add(new UInt8(1)).toString() ).toEqual(UInt8.MAXINT().toString()); }); @@ -2646,11 +2632,11 @@ describe('int', () => { describe('sub', () => { it('1-1=0', () => { - expect(new UInt8(Field(1)).sub(1).toString()).toEqual('0'); + expect(new UInt8(1).sub(1).toString()).toEqual('0'); }); it('100-50=50', () => { - expect(new UInt8(Field(100)).sub(50).toString()).toEqual('50'); + expect(new UInt8(100).sub(50).toString()).toEqual('50'); }); it('should throw on sub if results in negative number', () => { @@ -2662,15 +2648,15 @@ describe('int', () => { describe('mul', () => { it('1x2=2', () => { - expect(new UInt8(Field(1)).mul(2).toString()).toEqual('2'); + expect(new UInt8(1).mul(2).toString()).toEqual('2'); }); it('1x0=0', () => { - expect(new UInt8(Field(1)).mul(0).toString()).toEqual('0'); + expect(new UInt8(1).mul(0).toString()).toEqual('0'); }); it('12x20=240', () => { - expect(new UInt8(Field(12)).mul(20).toString()).toEqual('240'); + expect(new UInt8(12).mul(20).toString()).toEqual('240'); }); it('MAXINTx1=MAXINT', () => { @@ -2688,15 +2674,15 @@ describe('int', () => { describe('div', () => { it('2/1=2', () => { - expect(new UInt8(Field(2)).div(1).toString()).toEqual('2'); + expect(new UInt8(2).div(1).toString()).toEqual('2'); }); it('0/1=0', () => { - expect(new UInt32(Field(0)).div(1).toString()).toEqual('0'); + expect(new UInt8(0).div(1).toString()).toEqual('0'); }); it('20/10=2', () => { - expect(new UInt8(Field(20)).div(10).toString()).toEqual('2'); + expect(new UInt8(20).div(10).toString()).toEqual('2'); }); it('MAXINT/1=MAXINT', () => { @@ -2714,11 +2700,11 @@ describe('int', () => { describe('mod', () => { it('1%1=0', () => { - expect(new UInt8(Field(1)).mod(1).toString()).toEqual('0'); + expect(new UInt8(1).mod(1).toString()).toEqual('0'); }); it('50%32=18', () => { - expect(new UInt8(Field(50)).mod(32).toString()).toEqual('18'); + expect(new UInt8(50).mod(32).toString()).toEqual('18'); }); it('MAXINT%7=3', () => { @@ -2734,33 +2720,23 @@ describe('int', () => { describe('lessThan', () => { it('1<2=true', () => { - expect(new UInt8(Field(1)).lessThan(new UInt8(Field(2)))).toEqual( - Bool(true) - ); + expect(new UInt8(1).lessThan(new UInt8(2))).toEqual(Bool(true)); }); it('1<1=false', () => { - expect(new UInt8(Field(1)).lessThan(new UInt8(Field(1)))).toEqual( - Bool(false) - ); + expect(new UInt8(1).lessThan(new UInt8(1))).toEqual(Bool(false)); }); it('2<1=false', () => { - expect(new UInt8(Field(2)).lessThan(new UInt8(Field(1)))).toEqual( - Bool(false) - ); + expect(new UInt8(2).lessThan(new UInt8(1))).toEqual(Bool(false)); }); it('10<100=true', () => { - expect(new UInt8(Field(10)).lessThan(new UInt8(Field(100)))).toEqual( - Bool(true) - ); + expect(new UInt8(10).lessThan(new UInt8(100))).toEqual(Bool(true)); }); it('100<10=false', () => { - expect(new UInt8(Field(100)).lessThan(new UInt8(Field(10)))).toEqual( - Bool(false) - ); + expect(new UInt8(100).lessThan(new UInt8(10))).toEqual(Bool(false)); }); it('MAXINT { @@ -2770,27 +2746,27 @@ describe('int', () => { describe('lessThanOrEqual', () => { it('1<=1=true', () => { - expect( - new UInt8(Field(1)).lessThanOrEqual(new UInt8(Field(1))) - ).toEqual(Bool(true)); + expect(new UInt8(1).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); }); it('2<=1=false', () => { - expect( - new UInt8(Field(2)).lessThanOrEqual(new UInt8(Field(1))) - ).toEqual(Bool(false)); + expect(new UInt8(2).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(false) + ); }); it('10<=100=true', () => { - expect( - new UInt8(Field(10)).lessThanOrEqual(new UInt8(Field(100))) - ).toEqual(Bool(true)); + expect(new UInt8(10).lessThanOrEqual(new UInt8(100))).toEqual( + Bool(true) + ); }); it('100<=10=false', () => { - expect( - new UInt8(Field(100)).lessThanOrEqual(new UInt8(Field(10))) - ).toEqual(Bool(false)); + expect(new UInt8(100).lessThanOrEqual(new UInt8(10))).toEqual( + Bool(false) + ); }); it('MAXINT<=MAXINT=true', () => { @@ -2803,25 +2779,25 @@ describe('int', () => { describe('assertLessThanOrEqual', () => { it('1<=1=true', () => { expect(() => { - new UInt8(Field(1)).assertLessThanOrEqual(new UInt8(Field(1))); + new UInt8(1).assertLessThanOrEqual(new UInt8(1)); }).not.toThrow(); }); it('2<=1=false', () => { expect(() => { - new UInt8(Field(2)).assertLessThanOrEqual(new UInt8(Field(1))); + new UInt8(2).assertLessThanOrEqual(new UInt8(1)); }).toThrow(); }); it('10<=100=true', () => { expect(() => { - new UInt8(Field(10)).assertLessThanOrEqual(new UInt8(Field(100))); + new UInt8(10).assertLessThanOrEqual(new UInt8(100)); }).not.toThrow(); }); it('100<=10=false', () => { expect(() => { - new UInt8(Field(100)).assertLessThanOrEqual(new UInt8(Field(10))); + new UInt8(100).assertLessThanOrEqual(new UInt8(10)); }).toThrow(); }); @@ -2834,33 +2810,25 @@ describe('int', () => { describe('greaterThan', () => { it('2>1=true', () => { - expect(new UInt8(Field(2)).greaterThan(new UInt8(Field(1)))).toEqual( - Bool(true) - ); + expect(new UInt8(2).greaterThan(new UInt8(1))).toEqual(Bool(true)); }); it('1>1=false', () => { - expect(new UInt8(Field(1)).greaterThan(new UInt8(Field(1)))).toEqual( - Bool(false) - ); + expect(new UInt8(1).greaterThan(new UInt8(1))).toEqual(Bool(false)); }); it('1>2=false', () => { - expect(new UInt8(Field(1)).greaterThan(new UInt8(Field(2)))).toEqual( - Bool(false) - ); + expect(new UInt8(1).greaterThan(new UInt8(2))).toEqual(Bool(false)); }); it('100>10=true', () => { - expect( - new UInt8(Field(100)).greaterThan(new UInt8(Field(10))) - ).toEqual(Bool(true)); + expect(new UInt8(100).greaterThan(new UInt8(10))).toEqual(Bool(true)); }); it('10>100=false', () => { - expect( - new UInt8(Field(10)).greaterThan(new UInt8(Field(100))) - ).toEqual(Bool(false)); + expect(new UInt8(10).greaterThan(new UInt8(100))).toEqual( + Bool(false) + ); }); it('MAXINT>MAXINT=false', () => { @@ -2873,25 +2841,25 @@ describe('int', () => { describe('assertGreaterThan', () => { it('1>1=false', () => { expect(() => { - new UInt8(Field(1)).assertGreaterThan(new UInt8(Field(1))); + new UInt8(1).assertGreaterThan(new UInt8(1)); }).toThrow(); }); it('2>1=true', () => { expect(() => { - new UInt8(Field(2)).assertGreaterThan(new UInt8(Field(1))); + new UInt8(2).assertGreaterThan(new UInt8(1)); }).not.toThrow(); }); it('10>100=false', () => { expect(() => { - new UInt8(Field(10)).assertGreaterThan(new UInt8(Field(100))); + new UInt8(10).assertGreaterThan(new UInt8(100)); }).toThrow(); }); it('100000>1000=true', () => { expect(() => { - new UInt8(Field(100)).assertGreaterThan(new UInt8(Field(10))); + new UInt8(100).assertGreaterThan(new UInt8(10)); }).not.toThrow(); }); @@ -2904,33 +2872,33 @@ describe('int', () => { describe('greaterThanOrEqual', () => { it('2>=1=true', () => { - expect( - new UInt8(Field(2)).greaterThanOrEqual(new UInt8(Field(1))) - ).toEqual(Bool(true)); + expect(new UInt8(2).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); }); it('1>=1=true', () => { - expect( - new UInt8(Field(1)).greaterThanOrEqual(new UInt8(Field(1))) - ).toEqual(Bool(true)); + expect(new UInt8(1).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); }); it('1>=2=false', () => { - expect( - new UInt8(Field(1)).greaterThanOrEqual(new UInt8(Field(2))) - ).toEqual(Bool(false)); + expect(new UInt8(1).greaterThanOrEqual(new UInt8(2))).toEqual( + Bool(false) + ); }); it('100>=10=true', () => { - expect( - new UInt8(Field(100)).greaterThanOrEqual(new UInt8(Field(10))) - ).toEqual(Bool(true)); + expect(new UInt8(100).greaterThanOrEqual(new UInt8(10))).toEqual( + Bool(true) + ); }); it('10>=100=false', () => { - expect( - new UInt8(Field(10)).greaterThanOrEqual(new UInt8(Field(100))) - ).toEqual(Bool(false)); + expect(new UInt8(10).greaterThanOrEqual(new UInt8(100))).toEqual( + Bool(false) + ); }); it('MAXINT>=MAXINT=true', () => { @@ -2943,29 +2911,25 @@ describe('int', () => { describe('assertGreaterThanOrEqual', () => { it('1>=1=true', () => { expect(() => { - new UInt8(Field(1)).assertGreaterThanOrEqual(new UInt8(Field(1))); + new UInt8(1).assertGreaterThanOrEqual(new UInt8(1)); }).not.toThrow(); }); it('2>=1=true', () => { expect(() => { - new UInt8(Field(2)).assertGreaterThanOrEqual(new UInt8(Field(1))); + new UInt8(2).assertGreaterThanOrEqual(new UInt8(1)); }).not.toThrow(); }); it('10>=100=false', () => { expect(() => { - new UInt8(Field(10)).assertGreaterThanOrEqual( - new UInt8(Field(100)) - ); + new UInt8(10).assertGreaterThanOrEqual(new UInt8(100)); }).toThrow(); }); it('100>=10=true', () => { expect(() => { - new UInt8(Field(100)).assertGreaterThanOrEqual( - new UInt8(Field(10)) - ); + new UInt8(100).assertGreaterThanOrEqual(new UInt8(10)); }).not.toThrow(); }); @@ -2978,12 +2942,12 @@ describe('int', () => { describe('toString()', () => { it('should be the same as Field(0)', async () => { - const x = new UInt8(Field(0)); + const x = new UInt8(0); const y = Field(0); expect(x.toString()).toEqual(y.toString()); }); it('should be the same as 2^8-1', async () => { - const x = new UInt8(Field(String(NUMBERMAX))); + const x = new UInt8(NUMBERMAX.toBigInt()); const y = Field(String(NUMBERMAX)); expect(x.toString()).toEqual(y.toString()); }); @@ -3008,23 +2972,12 @@ describe('int', () => { describe('fromNumber()', () => { it('should be the same as Field(1)', () => { const x = UInt8.from(1); - expect(x.value).toEqual(new UInt32(Field(1)).value); + expect(x.value).toEqual(Field(1)); }); it('should be the same as 2^53-1', () => { const x = UInt8.from(NUMBERMAX); - expect(x.value).toEqual(Field(String(NUMBERMAX))); - }); - }); - describe('fromString()', () => { - it('should be the same as Field(1)', () => { - const x = UInt8.from('1'); - expect(x.value).toEqual(new UInt32(Field(1)).value); - }); - - it('should be the same as 2^53-1', () => { - const x = UInt8.from(String(NUMBERMAX)); - expect(x.value).toEqual(Field(String(NUMBERMAX))); + expect(x.value).toEqual(NUMBERMAX); }); }); }); From 7b6ace4f3d5be77440eee8451004e27323993134 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 12:56:43 +0100 Subject: [PATCH 1158/1215] bytes to test utils --- src/lib/gadgets/test-utils.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/test-utils.ts b/src/lib/gadgets/test-utils.ts index 309dac6161..96c78866e3 100644 --- a/src/lib/gadgets/test-utils.ts +++ b/src/lib/gadgets/test-utils.ts @@ -1,10 +1,17 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; -import { ProvableSpec } from '../testing/equivalent.js'; +import { ProvableSpec, spec } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; import { Gadgets } from './gadgets.js'; import { assert } from './common.js'; +import { Bytes } from '../provable-types/provable-types.js'; -export { foreignField, unreducedForeignField, uniformForeignField, throwError }; +export { + foreignField, + unreducedForeignField, + uniformForeignField, + bytes, + throwError, +}; const { Field3 } = Gadgets; @@ -49,6 +56,16 @@ function uniformForeignField( }; } +function bytes(length: number) { + const Bytes_ = Bytes(length); + return spec({ + rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), + there: Bytes_.from, + back: (x) => x.toBytes(), + provable: Bytes_.provable, + }); +} + // helper function throwError(message: string): T { From 8d52779bc144fb3edf66c178e4119851c00e1532 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 13:02:15 +0100 Subject: [PATCH 1159/1215] merge old and new unit tests --- src/lib/keccak-old.unit-test.ts | 272 -------------------------------- src/lib/keccak.unit-test.ts | 168 ++++++++++++++++++-- 2 files changed, 156 insertions(+), 284 deletions(-) delete mode 100644 src/lib/keccak-old.unit-test.ts diff --git a/src/lib/keccak-old.unit-test.ts b/src/lib/keccak-old.unit-test.ts deleted file mode 100644 index a39179f60a..0000000000 --- a/src/lib/keccak-old.unit-test.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { test, Random } from './testing/property.js'; -import { UInt8 } from './int.js'; -import { Hash } from './hashes-combined.js'; -import { Provable } from './provable.js'; -import { expect } from 'expect'; -import assert from 'assert'; -import { Bytes } from './provable-types/provable-types.js'; - -let RandomUInt8 = Random.map(Random.uint8, (x) => UInt8.from(x)); - -// Test constructor -test(Random.uint8, Random.uint8, (x, y, assert) => { - let z = new UInt8(x); - assert(z instanceof UInt8); - assert(z.toBigInt() === x); - assert(z.toString() === x.toString()); - - assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); - assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); - assert((z = new UInt8(z.value.value)) instanceof UInt8 && z.toBigInt() === x); - - z = new UInt8(y); - assert(z instanceof UInt8); - assert(z.toString() === y.toString()); -}); - -// handles all numbers up to 2^8 -test(Random.nat(255), (n, assert) => { - assert(UInt8.from(n).toString() === String(n)); -}); - -// throws on negative numbers -test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); - -// throws on numbers >= 2^8 -test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); - -runHashFunctionTests(); -console.log('OCaml tests pass! 🎉'); - -// test digest->hex and hex->digest conversions -checkHashInCircuit(); -console.log('hashing digest conversions matches! 🎉'); - -// check in-circuit -function checkHashInCircuit() { - Provable.runAndCheck(() => { - let data = Random.array(RandomUInt8, Random.nat(32)) - .create()() - .map((x) => Provable.witness(UInt8, () => UInt8.from(x))); - - checkHashConversions(Bytes.from(data)); - }); -} - -function checkHashConversions(data: Bytes) { - Provable.asProver(() => { - expectDigestToEqualHex(Hash.SHA3_256.hash(data)); - expectDigestToEqualHex(Hash.SHA3_384.hash(data)); - expectDigestToEqualHex(Hash.SHA3_512.hash(data)); - expectDigestToEqualHex(Hash.Keccak256.hash(data)); - }); -} - -function expectDigestToEqualHex(digest: Bytes) { - const hex = digest.toHex(); - expect(digest).toEqual(Bytes.fromHex(hex)); -} - -function equals(a: Bytes, b: Bytes): boolean { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) a.bytes[i].assertEquals(b.bytes[i]); - return true; -} - -/** - * Based off the following unit tests from the OCaml implementation: - * https://github.com/MinaProtocol/mina/blob/69d6ea4a3b7ca1690cf8f41d4598cb7484359e1d/src/lib/crypto/kimchi_backend/gadgets/keccak.ml#L646 - */ -function runHashFunctionTests() { - // Positive Tests - testExpected({ - nist: false, - length: 256, - message: '30', - expected: - '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', - }); - - testExpected({ - nist: true, - length: 512, - message: '30', - expected: - '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', - }); - - testExpected({ - nist: false, - length: 256, - message: - '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', - expected: - '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', - }); - - testExpected({ - nist: false, - length: 256, - message: - '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', - expected: - '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', - }); - - testExpected({ - nist: true, - length: 256, - message: - '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', - expected: - '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', - }); - - testExpected({ - nist: false, - length: 256, - message: - '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', - expected: - '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', - }); - - testExpected({ - nist: false, - length: 256, - message: - 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', - expected: - '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', - }); - - testExpected({ - nist: false, - length: 256, - message: - '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - expected: - 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', - }); - - testExpected({ - nist: false, - length: 256, - message: - '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - expected: - 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', - }); - - testExpected({ - nist: false, - length: 256, - message: 'a2c0', - expected: - '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', - }); - - testExpected({ - nist: false, - length: 256, - message: '0a2c', - expected: - '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', - }); - - testExpected({ - nist: false, - length: 256, - message: '00', - expected: - 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', - }); - - // Negative tests - try { - testExpected({ - nist: false, - length: 256, - message: 'a2c', - expected: - '07f02d241eeba9c909a1be75e08d9e8ac3e61d9e24fa452a6785083e1527c467', - }); - assert(false, 'Expected to throw'); - } catch (e) {} - - try { - testExpected({ - nist: true, - length: 256, - message: '0', - expected: - 'f39f4526920bb4c096e5722d64161ea0eb6dbd0b4ff0d812f31d56fb96142084', - }); - assert(false, 'Expected to throw'); - } catch (e) {} - - try { - testExpected({ - nist: true, - length: 256, - message: '30', - expected: - 'f9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e4', - }); - assert(false, 'Expected to throw'); - } catch (e) {} - - try { - testExpected({ - nist: true, - length: 256, - message: - '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', - expected: - '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', - }); - assert(false, 'Expected to throw'); - } catch (e) {} -} - -function testExpected({ - message, - expected, - nist = false, - length = 256, -}: { - message: string; - expected: string; - nist: boolean; - length: number; -}) { - Provable.runAndCheck(() => { - assert(message.length % 2 === 0); - - let fields = Bytes.fromHex(message); - let expectedHash = Bytes.fromHex(expected); - - Provable.asProver(() => { - if (nist) { - let hashed: Bytes; - switch (length) { - case 256: - hashed = Hash.SHA3_256.hash(fields); - break; - case 384: - hashed = Hash.SHA3_384.hash(fields); - break; - case 512: - hashed = Hash.SHA3_512.hash(fields); - break; - default: - assert(false); - } - equals(hashed!, expectedHash); - } else { - let hashed = Hash.Keccak256.hash(fields); - equals(hashed, expectedHash); - } - }); - }); -} diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index a811b018d9..2e4c160b3f 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,7 +1,6 @@ import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; -import { Random, sample } from './testing/random.js'; -import { equivalent, equivalentAsync, spec } from './testing/equivalent.js'; +import { equivalent, equivalentAsync } from './testing/equivalent.js'; import { keccak_224, keccak_256, @@ -13,6 +12,10 @@ import { sha3_512, } from '@noble/hashes/sha3'; import { Bytes } from './provable-types/provable-types.js'; +import { bytes } from './gadgets/test-utils.js'; +import { UInt8 } from './int.js'; +import { test, Random, sample } from './testing/property.js'; +import { expect } from 'expect'; const RUNS = 1; @@ -33,19 +36,11 @@ const testImplementations = { const lengths = [256, 384, 512] as const; +// EQUIVALENCE TESTS AGAINST REF IMPLEMENTATION + // checks outside circuit // TODO: fix witness generation slowness -const bytes = (length: number) => { - const Bytes_ = Bytes(length); - return spec({ - rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), - there: Bytes_.from, - back: (x) => x.toBytes(), - provable: Bytes_.provable, - }); -}; - for (let length of lengths) { let [preimageLength] = sample(Random.nat(100), 1); console.log(`Testing ${length} with preimage length ${preimageLength}`); @@ -63,8 +58,54 @@ for (let length of lengths) { (x) => Keccak.preNist(length, x), `keccak ${length}` ); + + // bytes to hex roundtrip + equivalent({ from: [inputBytes], to: inputBytes })( + (x) => x, + (x) => Bytes.fromHex(x.toHex()), + `Bytes toHex` + ); } +// EQUIVALENCE TESTS AGAINST TEST VECTORS (at the bottom) + +for (let { nist, length, message, expected } of testVectors()) { + let Hash = nist ? Keccak.nistSha3 : Keccak.preNist; + let actual = Hash(length, Bytes.fromHex(message)); + expect(actual).toEqual(Bytes.fromHex(expected)); +} + +// MISC QUICK TESTS + +// Test constructor +test(Random.uint8, Random.uint8, (x, y, assert) => { + let z = new UInt8(x); + assert(z instanceof UInt8); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + + assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value.value)) instanceof UInt8 && z.toBigInt() === x); + + z = new UInt8(y); + assert(z instanceof UInt8); + assert(z.toString() === y.toString()); +}); + +// handles all numbers up to 2^8 +test(Random.nat(255), (n, assert) => { + assert(UInt8.from(n).toString() === String(n)); +}); + +// throws on negative numbers +test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); + +// throws on numbers >= 2^8 +test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); + +// PROOF TESTS + // Choose a test length at random const digestLength = lengths[Math.floor(Math.random() * 3)]; @@ -121,3 +162,106 @@ await equivalentAsync( await KeccakProgram.verify(proof); return proof.publicOutput; }); + +// TEST VECTORS + +function testVectors(): { + nist: boolean; + length: 256 | 384 | 512; + message: string; + expected: string; +}[] { + return [ + { + nist: false, + length: 256, + message: '30', + expected: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', + }, + { + nist: true, + length: 512, + message: '30', + expected: + '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', + }, + { + nist: false, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }, + { + nist: false, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', + }, + { + nist: true, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', + }, + { + nist: false, + length: 256, + message: + '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', + }, + { + nist: false, + length: 256, + message: + 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: 'a2c0', + expected: + '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', + }, + { + nist: false, + length: 256, + message: '0a2c', + expected: + '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', + }, + { + nist: false, + length: 256, + message: '00', + expected: + 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', + }, + ]; +} From d2d5105b2575861031c7451ff135079281504f45 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 14 Dec 2023 13:36:52 +0100 Subject: [PATCH 1160/1215] minor, comment fix --- src/examples/zkapps/reducer/map.ts | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts index 176a277732..8e04cd1cc4 100644 --- a/src/examples/zkapps/reducer/map.ts +++ b/src/examples/zkapps/reducer/map.ts @@ -98,14 +98,12 @@ console.log(`method size for a "mapping" contract with ${k} entries`); console.log('get rows:', cs['get'].rows); console.log('set rows:', cs['set'].rows); -// a test account that pays all the fees, and puts additional funds into the zkapp +// a test account that pays all the fees let feePayerKey = Local.testAccounts[0].privateKey; let feePayer = Local.testAccounts[0].publicKey; // the zkapp account -let zkappKey = PrivateKey.fromBase58( - 'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD' -); +let zkappKey = PrivateKey.random(); let zkappAddress = zkappKey.toPublicKey(); let zkapp = new StorageContract(zkappAddress); @@ -119,19 +117,20 @@ await tx.sign([feePayerKey, zkappKey]).send(); console.log('deployed'); -let map: { key: PublicKey; value: Field }[] = []; -map[0] = { - key: PrivateKey.random().toPublicKey(), - value: Field(192), -}; -map[1] = { - key: PrivateKey.random().toPublicKey(), - value: Field(151), -}; -map[2] = { - key: PrivateKey.random().toPublicKey(), - value: Field(781), -}; +let map: { key: PublicKey; value: Field }[] = [ + { + key: PrivateKey.random().toPublicKey(), + value: Field(192), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(151), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(781), + }, +]; let key = map[0].key; let value = map[0].value; From f84a6e0d2e0c8285308a661352ef59df9e1ddb18 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Thu, 14 Dec 2023 13:40:02 +0100 Subject: [PATCH 1161/1215] remove _, auto type inference --- src/examples/zkapps/reducer/map.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts index 8e04cd1cc4..890555be92 100644 --- a/src/examples/zkapps/reducer/map.ts +++ b/src/examples/zkapps/reducer/map.ts @@ -64,17 +64,11 @@ class StorageContract extends SmartContract { let { state: optionValue } = this.reducer.reduce( pendingActions, Option, - ( - _state: Option, - _action: { - key: Field; - value: Field; - } - ) => { - let currentMatch = keyHash.equals(_action.key); + (state, action) => { + let currentMatch = keyHash.equals(action.key); return { - isSome: currentMatch.or(_state.isSome), - value: Provable.if(currentMatch, _action.value, _state.value), + isSome: currentMatch.or(state.isSome), + value: Provable.if(currentMatch, action.value, state.value), }; }, { From a02888e3ba172713b3a90bf2dfeecc49b439611b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 15:05:54 +0100 Subject: [PATCH 1162/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7946599c5f..61c75c037d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7946599c5f1636576519601dbd2c20aecc90a502 +Subproject commit 61c75c037db058231f4e1b13a4743178b95d0aa0 From c5a54fd45b261287cfd6f9fb6961b88e1152adc3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:25:07 +0100 Subject: [PATCH 1163/1215] remove private class field from Field --- src/lib/field.ts | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 226689979f..9c33016624 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -156,7 +156,7 @@ class Field { * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a Field. */ constructor(x: bigint | number | string | Field | FieldVar | FieldConst) { - if (Field.#isField(x)) { + if (x instanceof Field) { this.value = x.value; return; } @@ -176,21 +176,9 @@ class Field { } // helpers - static #isField( - x: bigint | number | string | Field | FieldVar | FieldConst - ): x is Field { - return x instanceof Field; - } - static #toConst(x: bigint | number | string | ConstantField): FieldConst { - if (Field.#isField(x)) return x.value[1]; - return FieldConst.fromBigint(Fp(x)); - } - static #toVar(x: bigint | number | string | Field): FieldVar { - if (Field.#isField(x)) return x.value; - return FieldVar.constant(Fp(x)); - } + static from(x: bigint | number | string | Field): Field { - if (Field.#isField(x)) return x; + if (x instanceof Field) return x; return new Field(x); } @@ -216,10 +204,6 @@ class Field { return this.value[0] === FieldType.Constant; } - #toConstant(name: string): ConstantField { - return toConstantField(this, name, 'x', 'field element'); - } - /** * Create a {@link Field} element equivalent to this {@link Field} element's value, * but is a constant. @@ -234,7 +218,7 @@ class Field { * @return A constant {@link Field} element equivalent to this {@link Field} element. */ toConstant(): ConstantField { - return this.#toConstant('toConstant'); + return toConstant(this, 'toConstant'); } /** @@ -251,7 +235,7 @@ class Field { * @return A bigint equivalent to the bigint representation of the Field. */ toBigInt() { - let x = this.#toConstant('toBigInt'); + let x = toConstant(this, 'toBigInt'); return FieldConst.toBigint(x.value[1]); } @@ -269,7 +253,7 @@ class Field { * @return A string equivalent to the string representation of the Field. */ toString() { - return this.#toConstant('toString').toBigInt().toString(); + return toConstant(this, 'toString').toBigInt().toString(); } /** @@ -290,7 +274,7 @@ class Field { } return; } - Snarky.field.assertEqual(this.value, Field.#toVar(y)); + Snarky.field.assertEqual(this.value, toFieldVar(y)); } catch (err) { throw withMessage(err, message); } @@ -329,7 +313,7 @@ class Field { return new Field(Fp.add(this.toBigInt(), toFp(y))); } // return new AST node Add(x, y) - let z = Snarky.field.add(this.value, Field.#toVar(y)); + let z = Snarky.field.add(this.value, toFieldVar(y)); return new Field(z); } @@ -456,7 +440,7 @@ class Field { } // if one of the factors is constant, return Scale AST node if (isConstant(y)) { - let z = Snarky.field.scale(Field.#toConst(y), this.value); + let z = Snarky.field.scale(toFieldConst(y), this.value); return new Field(z); } if (this.isConstant()) { @@ -664,24 +648,6 @@ class Field { return new Field(xMinusY).isZero(); } - // internal base method for all comparisons - #compare(y: FieldVar) { - // TODO: support all bit lengths - let maxLength = Fp.sizeInBits - 2; - asProver(() => { - let actualLength = Math.max( - this.toBigInt().toString(2).length, - new Field(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.` - ); - }); - let [, less, lessOrEqual] = Snarky.field.compare(maxLength, this.value, y); - return { less: new Bool(less), lessOrEqual: new Bool(lessOrEqual) }; - } - /** * Check if this {@link Field} is less than another "field-like" value. * Returns a {@link Bool}, which is a provable type and can be used prove to the validity of this statement. @@ -709,7 +675,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() < toFp(y)); } - return this.#compare(Field.#toVar(y)).less; + return compare(this, toFieldVar(y)).less; } /** @@ -739,7 +705,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() <= toFp(y)); } - return this.#compare(Field.#toVar(y)).lessOrEqual; + return compare(this, toFieldVar(y)).lessOrEqual; } /** @@ -817,7 +783,7 @@ class Field { } return; } - let { less } = this.#compare(Field.#toVar(y)); + let { less } = compare(this, toFieldVar(y)); less.assertTrue(); } catch (err) { throw withMessage(err, message); @@ -845,7 +811,7 @@ class Field { } return; } - let { lessOrEqual } = this.#compare(Field.#toVar(y)); + let { lessOrEqual } = compare(this, toFieldVar(y)); lessOrEqual.assertTrue(); } catch (err) { throw withMessage(err, message); @@ -1165,7 +1131,7 @@ class Field { * @return A string equivalent to the JSON representation of the {@link Field}. */ toJSON() { - return this.#toConstant('toJSON').toString(); + return toConstant(this, 'toJSON').toString(); } /** @@ -1279,6 +1245,8 @@ const FieldBinable = defineBinable({ }, }); +// internal helper functions + function isField(x: unknown): x is Field { return x instanceof Field; } @@ -1301,12 +1269,40 @@ function toFp(x: bigint | number | string | Field): Fp { return (x as Field).toBigInt(); } +function toFieldConst(x: bigint | number | string | ConstantField): FieldConst { + if (x instanceof Field) return x.value[1]; + return FieldConst.fromBigint(Fp(x)); +} + +function toFieldVar(x: bigint | number | string | Field): FieldVar { + if (x instanceof Field) return x.value; + return FieldVar.constant(Fp(x)); +} + function withMessage(error: unknown, message?: string) { if (message === undefined || !(error instanceof Error)) return error; error.message = `${message}\n${error.message}`; return error; } +// internal base method for all comparisons +function compare(x: Field, y: FieldVar) { + // TODO: support all bit lengths + let maxLength = Fp.sizeInBits - 2; + asProver(() => { + let actualLength = Math.max( + x.toBigInt().toString(2).length, + new Field(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.` + ); + }); + let [, less, lessOrEqual] = Snarky.field.compare(maxLength, x.value, y); + return { less: new Bool(less), lessOrEqual: new Bool(lessOrEqual) }; +} + function checkBitLength( name: string, length: number, @@ -1320,6 +1316,10 @@ function checkBitLength( throw Error(`${name}: bit length must be non-negative, got ${length}`); } +function toConstant(x: Field, name: string): ConstantField { + return toConstantField(x, name, 'x', 'field element'); +} + function toConstantField( x: Field, methodName: string, From bce7914ca64b7badd654e14797fc5b2debd6af9d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:27:28 +0100 Subject: [PATCH 1164/1215] remove unnecessary helper functions --- src/lib/bool.ts | 6 +----- src/lib/field.ts | 5 ----- src/lib/group.ts | 6 +++--- src/lib/provable.ts | 8 +------- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 9d7fee9e4d..85d4fab607 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -11,7 +11,7 @@ import { defineBinable } from '../bindings/lib/binable.js'; import { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver } from './provable-context.js'; -export { BoolVar, Bool, isBool }; +export { BoolVar, Bool }; // same representation, but use a different name to communicate intent / constraints type BoolVar = FieldVar; @@ -370,10 +370,6 @@ function isConstant(x: boolean | Bool): x is boolean | ConstantBool { return x.isConstant(); } -function isBool(x: unknown) { - return x instanceof Bool; -} - function toBoolean(x: boolean | Bool): boolean { if (typeof x === 'boolean') { return x; diff --git a/src/lib/field.ts b/src/lib/field.ts index 9c33016624..891390619d 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -17,7 +17,6 @@ export { ConstantField, VarField, VarFieldVar, - isField, withMessage, readVarMessage, toConstantField, @@ -1247,10 +1246,6 @@ const FieldBinable = defineBinable({ // internal helper functions -function isField(x: unknown): x is Field { - return x instanceof Field; -} - function isConstant( x: bigint | number | string | Field ): x is bigint | number | string | ConstantField { diff --git a/src/lib/group.ts b/src/lib/group.ts index 6178032df6..4cd6d9e147 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -1,4 +1,4 @@ -import { Field, FieldVar, isField } from './field.js'; +import { Field, FieldVar } from './field.js'; import { Scalar } from './scalar.js'; import { Snarky } from '../snarky.js'; import { Field as Fp } from '../provable/field-bigint.js'; @@ -48,8 +48,8 @@ class Group { x: FieldVar | Field | number | string | bigint; y: FieldVar | Field | number | string | bigint; }) { - this.x = isField(x) ? x : new Field(x); - this.y = isField(y) ? y : new Field(y); + this.x = x instanceof Field ? x : new Field(x); + this.y = y instanceof Field ? y : new Field(y); if (this.#isConstant()) { // we also check the zero element (0, 0) here diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 534818f16f..8094bcf89e 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -13,7 +13,6 @@ import { InferProvable, InferredProvable, } from '../bindings/lib/provable-snarky.js'; -import { isField } from './field.js'; import { inCheckedComputation, inProver, @@ -23,7 +22,6 @@ import { runUnchecked, constraintSystem, } from './provable-context.js'; -import { isBool } from './bool.js'; // external API export { Provable }; @@ -345,11 +343,7 @@ function ifImplicit(condition: Bool, x: T, y: T): T { ); // TODO remove second condition once we have consolidated field class back into one // if (type !== y.constructor) { - if ( - type !== y.constructor && - !(isField(x) && isField(y)) && - !(isBool(x) && isBool(y)) - ) { + if (type !== y.constructor) { throw Error( 'Provable.if: Mismatched argument types. Try using an explicit type argument:\n' + `Provable.if(bool, MyType, x, y)` From 4ba40a12695c528cf0a265853a845b3f37f717db Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:30:39 +0100 Subject: [PATCH 1165/1215] remove private class fields from Bool --- src/lib/bool.ts | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 85d4fab607..8957e89d53 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -34,7 +34,7 @@ class Bool { value: BoolVar; constructor(x: boolean | Bool | BoolVar) { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { this.value = x.value; return; } @@ -75,7 +75,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() && toBoolean(y)); } - return new Bool(Snarky.bool.and(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.and(this.value, toFieldVar(y))); } /** @@ -87,7 +87,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() || toBoolean(y)); } - return new Bool(Snarky.bool.or(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.or(this.value, toFieldVar(y))); } /** @@ -102,7 +102,7 @@ class Bool { } return; } - Snarky.bool.assertEqual(this.value, Bool.#toVar(y)); + Snarky.bool.assertEqual(this.value, toFieldVar(y)); } catch (err) { throw withMessage(err, message); } @@ -144,7 +144,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() === toBoolean(y)); } - return new Bool(Snarky.bool.equals(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.equals(this.value, toFieldVar(y))); } /** @@ -194,14 +194,14 @@ class Bool { } static toField(x: Bool | boolean): Field { - return new Field(Bool.#toVar(x)); + return new Field(toFieldVar(x)); } /** * Boolean negation. */ static not(x: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.not(); } return new Bool(!x); @@ -211,7 +211,7 @@ class Bool { * Boolean AND operation. */ static and(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.and(y); } return new Bool(x).and(y); @@ -221,7 +221,7 @@ class Bool { * Boolean OR operation. */ static or(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.or(y); } return new Bool(x).or(y); @@ -231,7 +231,7 @@ class Bool { * Asserts if both {@link Bool} are equal. */ static assertEqual(x: Bool, y: Bool | boolean): void { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { x.assertEquals(y); return; } @@ -242,7 +242,7 @@ class Bool { * Checks two {@link Bool} for equality. */ static equal(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.equals(y); } return new Bool(x).equals(y); @@ -342,15 +342,6 @@ class Bool { return new Bool(x.value); }, }; - - static #isBool(x: boolean | Bool | BoolVar): x is Bool { - return x instanceof Bool; - } - - static #toVar(x: boolean | Bool): BoolVar { - if (Bool.#isBool(x)) return x.value; - return FieldVar.constant(B(x)); - } } const BoolBinable = defineBinable({ @@ -362,6 +353,8 @@ const BoolBinable = defineBinable({ }, }); +// internal helper functions + function isConstant(x: boolean | Bool): x is boolean | ConstantBool { if (typeof x === 'boolean') { return true; @@ -377,6 +370,11 @@ function toBoolean(x: boolean | Bool): boolean { return x.toBoolean(); } +function toFieldVar(x: boolean | Bool): BoolVar { + if (x instanceof Bool) return x.value; + return FieldVar.constant(B(x)); +} + // TODO: This is duplicated function withMessage(error: unknown, message?: string) { if (message === undefined || !(error instanceof Error)) return error; From 4f75418988263124f5f854311f1a65dcad7e6f1f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:37:45 +0100 Subject: [PATCH 1166/1215] remove private class fields from Group --- src/lib/group.ts | 73 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/lib/group.ts b/src/lib/group.ts index 4cd6d9e147..67b021097e 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -51,7 +51,7 @@ class Group { this.x = x instanceof Field ? x : new Field(x); this.y = y instanceof Field ? y : new Field(y); - if (this.#isConstant()) { + if (isConstant(this)) { // we also check the zero element (0, 0) here if (this.x.equals(0).and(this.y.equals(0)).toBoolean()) return; @@ -72,31 +72,6 @@ class Group { } } - // helpers - static #fromAffine({ x, y, infinity }: GroupAffine) { - return infinity ? Group.zero : new Group({ x, y }); - } - - static #fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { - return this.#fromAffine(Pallas.toAffine({ x, y, z })); - } - - #toTuple(): [0, FieldVar, FieldVar] { - return [0, this.x.value, this.y.value]; - } - - #isConstant() { - return this.x.isConstant() && this.y.isConstant(); - } - - #toProjective() { - return Pallas.fromAffine({ - x: this.x.toBigInt(), - y: this.y.toBigInt(), - infinity: false, - }); - } - /** * Checks if this element is the `zero` element `{x: 0, y: 0}`. */ @@ -114,15 +89,15 @@ class Group { * ``` */ add(g: Group) { - if (this.#isConstant() && g.#isConstant()) { + if (isConstant(this) && isConstant(g)) { // we check if either operand is zero, because adding zero to g just results in g (and vise versa) if (this.isZero().toBoolean()) { return g; } else if (g.isZero().toBoolean()) { return this; } else { - let g_proj = Pallas.add(this.#toProjective(), g.#toProjective()); - return Group.#fromProjective(g_proj); + let g_proj = Pallas.add(toProjective(this), toProjective(g)); + return fromProjective(g_proj); } } else { const { x: x1, y: y1 } = this; @@ -163,9 +138,9 @@ class Group { }); let [, x, y] = Snarky.gates.ecAdd( - Group.from(x1.seal(), y1.seal()).#toTuple(), - Group.from(x2.seal(), y2.seal()).#toTuple(), - Group.from(x3, y3).#toTuple(), + 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, @@ -216,13 +191,13 @@ class Group { scale(s: Scalar | number | bigint) { let scalar = Scalar.from(s); - if (this.#isConstant() && scalar.isConstant()) { - let g_proj = Pallas.scale(this.#toProjective(), scalar.toBigInt()); - return Group.#fromProjective(g_proj); + if (isConstant(this) && scalar.isConstant()) { + let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); + return fromProjective(g_proj); } else { let [, ...bits] = scalar.value; bits.reverse(); - let [, x, y] = Snarky.group.scale(this.#toTuple(), [0, ...bits]); + let [, x, y] = Snarky.group.scale(toTuple(this), [0, ...bits]); return new Group({ x, y }); } } @@ -448,3 +423,29 @@ class Group { } } } + +// internal helpers + +function isConstant(g: Group) { + return g.x.isConstant() && g.y.isConstant(); +} + +function toTuple(g: Group): [0, FieldVar, FieldVar] { + return [0, g.x.value, g.y.value]; +} + +function toProjective(g: Group) { + return Pallas.fromAffine({ + x: g.x.toBigInt(), + y: g.y.toBigInt(), + infinity: false, + }); +} + +function fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { + return fromAffine(Pallas.toAffine({ x, y, z })); +} + +function fromAffine({ x, y, infinity }: GroupAffine) { + return infinity ? Group.zero : new Group({ x, y }); +} From 9690a5b42eca15e32081ba539d35652917198c80 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:44:08 +0100 Subject: [PATCH 1167/1215] remove private class Fields from Scalar --- src/lib/scalar.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 176478947f..d54f20c209 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -88,7 +88,7 @@ class Scalar { * Convert this {@link Scalar} into a bigint */ toBigInt() { - return this.#assertConstant('toBigInt'); + return assertConstant(this, 'toBigInt'); } // TODO: fix this API. we should represent "shifted status" internally and use @@ -112,17 +112,13 @@ class Scalar { // operations on constant scalars - #assertConstant(name: string) { - return constantScalarToBigint(this, `Scalar.${name}`); - } - /** * Negate a scalar field element. * * **Warning**: This method is not available for provable code. */ neg() { - let x = this.#assertConstant('neg'); + let x = assertConstant(this, 'neg'); let z = Fq.negate(x); return Scalar.from(z); } @@ -133,8 +129,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ add(y: Scalar) { - let x = this.#assertConstant('add'); - let y0 = y.#assertConstant('add'); + let x = assertConstant(this, 'add'); + let y0 = assertConstant(y, 'add'); let z = Fq.add(x, y0); return Scalar.from(z); } @@ -145,8 +141,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ sub(y: Scalar) { - let x = this.#assertConstant('sub'); - let y0 = y.#assertConstant('sub'); + let x = assertConstant(this, 'sub'); + let y0 = assertConstant(y, 'sub'); let z = Fq.sub(x, y0); return Scalar.from(z); } @@ -157,8 +153,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ mul(y: Scalar) { - let x = this.#assertConstant('mul'); - let y0 = y.#assertConstant('mul'); + let x = assertConstant(this, 'mul'); + let y0 = assertConstant(y, 'mul'); let z = Fq.mul(x, y0); return Scalar.from(z); } @@ -170,8 +166,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ div(y: Scalar) { - let x = this.#assertConstant('div'); - let y0 = y.#assertConstant('div'); + let x = assertConstant(this, 'div'); + let y0 = assertConstant(y, 'div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.from(z); @@ -179,11 +175,11 @@ class Scalar { // TODO don't leak 'shifting' to the user and remove these methods shift() { - let x = this.#assertConstant('shift'); + let x = assertConstant(this, 'shift'); return Scalar.from(shift(x)); } unshift() { - let x = this.#assertConstant('unshift'); + let x = assertConstant(this, 'unshift'); return Scalar.from(unshift(x)); } @@ -196,7 +192,7 @@ class Scalar { * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. */ toFieldsCompressed(): { field: Field; highBit: Bool } { - let s = this.#assertConstant('toFieldsCompressed'); + let s = assertConstant(this, 'toFieldsCompressed'); let lowBitSize = BigInt(Fq.sizeInBits - 1); let lowBitMask = (1n << lowBitSize) - 1n; return { @@ -292,7 +288,7 @@ class Scalar { * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static toJSON(x: Scalar) { - let s = x.#assertConstant('toJSON'); + let s = assertConstant(x, 'toJSON'); return s.toString(); } @@ -312,6 +308,12 @@ class Scalar { } } +// internal helpers + +function assertConstant(x: Scalar, name: string) { + return constantScalarToBigint(x, `Scalar.${name}`); +} + function toConstantScalar([, ...bits]: MlArray): Fq | undefined { if (bits.length !== Fq.sizeInBits) throw Error( From 96c756a1f95ebe1f932954db51f01285065fd178 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 16:54:29 +0100 Subject: [PATCH 1168/1215] remove private class fields from SmartContract --- src/lib/zkapp.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 6986966a3d..77387d367f 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -601,7 +601,7 @@ class SmartContract { address: PublicKey; tokenId: Field; - #executionState: ExecutionState | undefined; + private _executionState: ExecutionState | undefined; // here we store various metadata associated with a SmartContract subclass. // by initializing all of these to `undefined`, we ensure that @@ -758,8 +758,8 @@ class SmartContract { else this.init(); let initUpdate = this.self; // switch back to the deploy account update so the user can make modifications to it - this.#executionState = { - transactionId: this.#executionState!.transactionId, + this._executionState = { + transactionId: this._executionState!.transactionId, accountUpdate, }; // check if the entire state was overwritten, show a warning if not @@ -853,10 +853,10 @@ super.init(); // it won't create new updates and add them to a transaction implicitly if (inSmartContract && inSmartContract.this === this) { let accountUpdate = inSmartContract.selfUpdate; - this.#executionState = { accountUpdate, transactionId }; + this._executionState = { accountUpdate, transactionId }; return accountUpdate; } - let executionState = this.#executionState; + let executionState = this._executionState; if ( executionState !== undefined && executionState.transactionId === transactionId @@ -866,7 +866,7 @@ super.init(); // if in a transaction, but outside a @method call, we implicitly create an account update // which is stable during the current transaction -- as long as it doesn't get overridden by a method call let accountUpdate = selfAccountUpdate(this); - this.#executionState = { transactionId, accountUpdate }; + this._executionState = { transactionId, accountUpdate }; return accountUpdate; } // same as this.self, but explicitly creates a _new_ account update @@ -877,11 +877,11 @@ super.init(); let inTransaction = Mina.currentTransaction.has(); let transactionId = inTransaction ? Mina.currentTransaction.id() : NaN; let accountUpdate = selfAccountUpdate(this); - this.#executionState = { transactionId, accountUpdate }; + this._executionState = { transactionId, accountUpdate }; return accountUpdate; } - #_senderState: { sender: PublicKey; transactionId: number }; + private _senderState: { sender: PublicKey; transactionId: number }; /** * The public key of the current transaction's sender account. @@ -900,11 +900,11 @@ super.init(); ); } let transactionId = Mina.currentTransaction.id(); - if (this.#_senderState?.transactionId === transactionId) { - return this.#_senderState.sender; + if (this._senderState?.transactionId === transactionId) { + return this._senderState.sender; } else { let sender = Provable.witness(PublicKey, () => Mina.sender()); - this.#_senderState = { transactionId, sender }; + this._senderState = { transactionId, sender }; return sender; } } @@ -1195,7 +1195,7 @@ super.init(); publicInput, ...args ); - accountUpdate = instance.#executionState!.accountUpdate; + accountUpdate = instance._executionState!.accountUpdate; return result; } ); From c40e3f1a661f696c4f1f35515696f3e82a2ad99b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 14 Dec 2023 18:00:20 +0100 Subject: [PATCH 1169/1215] Revert "remove private class fields from SmartContract" This reverts commit 96c756a1f95ebe1f932954db51f01285065fd178. --- src/lib/zkapp.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 77387d367f..6986966a3d 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -601,7 +601,7 @@ class SmartContract { address: PublicKey; tokenId: Field; - private _executionState: ExecutionState | undefined; + #executionState: ExecutionState | undefined; // here we store various metadata associated with a SmartContract subclass. // by initializing all of these to `undefined`, we ensure that @@ -758,8 +758,8 @@ class SmartContract { else this.init(); let initUpdate = this.self; // switch back to the deploy account update so the user can make modifications to it - this._executionState = { - transactionId: this._executionState!.transactionId, + this.#executionState = { + transactionId: this.#executionState!.transactionId, accountUpdate, }; // check if the entire state was overwritten, show a warning if not @@ -853,10 +853,10 @@ super.init(); // it won't create new updates and add them to a transaction implicitly if (inSmartContract && inSmartContract.this === this) { let accountUpdate = inSmartContract.selfUpdate; - this._executionState = { accountUpdate, transactionId }; + this.#executionState = { accountUpdate, transactionId }; return accountUpdate; } - let executionState = this._executionState; + let executionState = this.#executionState; if ( executionState !== undefined && executionState.transactionId === transactionId @@ -866,7 +866,7 @@ super.init(); // if in a transaction, but outside a @method call, we implicitly create an account update // which is stable during the current transaction -- as long as it doesn't get overridden by a method call let accountUpdate = selfAccountUpdate(this); - this._executionState = { transactionId, accountUpdate }; + this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } // same as this.self, but explicitly creates a _new_ account update @@ -877,11 +877,11 @@ super.init(); let inTransaction = Mina.currentTransaction.has(); let transactionId = inTransaction ? Mina.currentTransaction.id() : NaN; let accountUpdate = selfAccountUpdate(this); - this._executionState = { transactionId, accountUpdate }; + this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } - private _senderState: { sender: PublicKey; transactionId: number }; + #_senderState: { sender: PublicKey; transactionId: number }; /** * The public key of the current transaction's sender account. @@ -900,11 +900,11 @@ super.init(); ); } let transactionId = Mina.currentTransaction.id(); - if (this._senderState?.transactionId === transactionId) { - return this._senderState.sender; + if (this.#_senderState?.transactionId === transactionId) { + return this.#_senderState.sender; } else { let sender = Provable.witness(PublicKey, () => Mina.sender()); - this._senderState = { transactionId, sender }; + this.#_senderState = { transactionId, sender }; return sender; } } @@ -1195,7 +1195,7 @@ super.init(); publicInput, ...args ); - accountUpdate = instance._executionState!.accountUpdate; + accountUpdate = instance.#executionState!.accountUpdate; return result; } ); From d29140189077ae07a3bad9a455374a81a1404bd8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Dec 2023 10:24:27 -0800 Subject: [PATCH 1170/1215] Update package.json Co-authored-by: Gregor Mitscha-Baude --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 9b65edfeaf..f58dd76fa1 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,11 @@ "node": ">=16.4.0" }, "scripts": { - "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", + "dev": "npx tsc -p tsconfig.test.json && node src/build/copy-to-dist.js", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", "build:bindings": "./src/bindings/scripts/build-snarkyjs-node.sh", "build:update-bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", "build:wasm": "./src/bindings/scripts/update-wasm-and-types.sh", - "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", - "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", "build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", From a2dde97f8f2c7f3117c66f6d15878045e537a80c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Dec 2023 10:26:21 -0800 Subject: [PATCH 1171/1215] style(run-jest-tests.sh): add newline at end of file to comply with POSIX standards --- run-jest-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-jest-tests.sh b/run-jest-tests.sh index b15b30d311..d103274798 100755 --- a/run-jest-tests.sh +++ b/run-jest-tests.sh @@ -4,4 +4,4 @@ shopt -s globstar # to expand '**' into nested directories for f in ./src/**/*.test.ts; do NODE_OPTIONS=--experimental-vm-modules npx jest $f; -done \ No newline at end of file +done From cfa4883147b525eb9da0eb3f9087feb46e2163d8 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 15 Dec 2023 11:56:28 +0100 Subject: [PATCH 1172/1215] return UInt instead of field --- src/lib/int.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 9deb220156..2da45696db 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -231,7 +231,7 @@ class UInt64 extends CircuitValue { * ``` */ xor(x: UInt64) { - return Gadgets.xor(this.value, x.value, UInt64.NUM_BITS); + return new UInt64(Gadgets.xor(this.value, x.value, UInt64.NUM_BITS)); } /** @@ -264,7 +264,7 @@ class UInt64 extends CircuitValue { * */ not() { - return Gadgets.not(this.value, UInt64.NUM_BITS, false); + return new UInt64(Gadgets.not(this.value, UInt64.NUM_BITS, false)); } /** @@ -296,7 +296,7 @@ class UInt64 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return Gadgets.rotate64(this.value, bits, direction); + return new UInt64(Gadgets.rotate64(this.value, bits, direction)); } /** @@ -317,7 +317,7 @@ class UInt64 extends CircuitValue { * ``` */ leftShift(bits: number) { - return Gadgets.leftShift64(this.value, bits); + return new UInt64(Gadgets.leftShift64(this.value, bits)); } /** @@ -338,7 +338,7 @@ class UInt64 extends CircuitValue { * ``` */ rightShift(bits: number) { - return Gadgets.leftShift64(this.value, bits); + return new UInt64(Gadgets.leftShift64(this.value, bits)); } /** @@ -367,7 +367,7 @@ class UInt64 extends CircuitValue { * ``` */ and(x: UInt64) { - return Gadgets.and(this.value, x.value, UInt64.NUM_BITS); + return new UInt64(Gadgets.and(this.value, x.value, UInt64.NUM_BITS)); } /** @@ -733,7 +733,7 @@ class UInt32 extends CircuitValue { * ``` */ xor(x: UInt32) { - return Gadgets.xor(this.value, x.value, UInt32.NUM_BITS); + return new UInt32(Gadgets.xor(this.value, x.value, UInt32.NUM_BITS)); } /** @@ -764,7 +764,7 @@ class UInt32 extends CircuitValue { * @param a - The value to apply NOT to. */ not() { - return Gadgets.not(this.value, UInt32.NUM_BITS, false); + return new UInt32(Gadgets.not(this.value, UInt32.NUM_BITS, false)); } /** @@ -796,7 +796,7 @@ class UInt32 extends CircuitValue { * ``` */ rotate(bits: number, direction: 'left' | 'right' = 'left') { - return Gadgets.rotate32(this.value, bits, direction); + return new UInt32(Gadgets.rotate32(this.value, bits, direction)); } /** @@ -819,7 +819,7 @@ class UInt32 extends CircuitValue { * ``` */ leftShift(bits: number) { - return Gadgets.leftShift32(this.value, bits); + return new UInt32(Gadgets.leftShift32(this.value, bits)); } /** @@ -842,7 +842,7 @@ class UInt32 extends CircuitValue { * ``` */ rightShift(bits: number) { - return Gadgets.rightShift64(this.value, bits); + return new UInt32(Gadgets.rightShift64(this.value, bits)); } /** From 97027930b2020e61f9abe56d63fc32c2eb2412f6 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 15 Dec 2023 11:58:00 +0100 Subject: [PATCH 1173/1215] fix compilation, bump bindings --- src/lib/keccak.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts index ac63ceecb1..ccc8c45548 100644 --- a/src/lib/keccak.ts +++ b/src/lib/keccak.ts @@ -205,7 +205,7 @@ const theta = (state: Field[][]): Field[][] => { const stateD = Array.from({ length: KECCAK_DIM }, (_, i) => xor( stateC[(i + KECCAK_DIM - 1) % KECCAK_DIM], - Gadgets.rotate(stateC[(i + 1) % KECCAK_DIM], 1, 'left') + Gadgets.rotate64(stateC[(i + 1) % KECCAK_DIM], 1, 'left') ) ); @@ -249,7 +249,7 @@ function piRho(state: Field[][]): Field[][] { // for all i in {0..4} and j in {0..4}: B[j,2i+3j] = ROT(E[i,j], r[i,j]) for (let i = 0; i < KECCAK_DIM; i++) { for (let j = 0; j < KECCAK_DIM; j++) { - stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate( + stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate64( stateE[i][j], ROT_TABLE[i][j], 'left' From 7b5e8fa866ec5434cc152252e3c6a18e8e409055 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 15 Dec 2023 11:58:55 +0100 Subject: [PATCH 1174/1215] fix doc comment --- src/lib/gadgets/gadgets.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index beda5fdd16..356f3371d7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -373,7 +373,7 @@ const Gadgets = { /** * Performs a right shift operation on the provided {@link Field} element. * This is similar to the `>>` shift operation in JavaScript, where bits are moved to the right. - * The `rightShift` function utilizes the rotation method internally to implement this operation. + * The `rightShift64` function utilizes the rotation method internally to implement this operation. * * * It’s important to note that these operations are performed considering the big-endian 64-bit representation of the number, * where the most significant (64th) bit is on the left end and the least significant bit is on the right end. @@ -381,7 +381,7 @@ const Gadgets = { * **Important:** The gadgets assumes that its input is at most 64 bits in size. * * If the input exceeds 64 bits, the gadget is invalid and fails to prove correct execution of the shift. - * To safely use `rightShift()`, you need to make sure that the value passed in is range-checked to 64 bits; + * To safely use `rightShift64()`, you need to make sure that the value passed in is range-checked to 64 bits; * for example, using {@link Gadgets.rangeCheck64}. * * @param field {@link Field} element to shift. @@ -396,7 +396,7 @@ const Gadgets = { * y.assertEquals(0b000011); // 3 in binary * * const xLarge = Provable.witness(Field, () => Field(12345678901234567890123456789012345678n)); - * rightShift(xLarge, 32); // throws an error since input exceeds 64 bits + * rightShift64(xLarge, 32); // throws an error since input exceeds 64 bits * ``` */ rightShift64(field: Field, bits: number) { From 5def65787fff26e7178037a41fdbeae28626b1dc Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 15 Dec 2023 12:18:23 +0100 Subject: [PATCH 1175/1215] set proofs enabled on object to make updated value readable from outside --- src/lib/mina.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index d4a55c6a8b..e3b5823a9c 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -482,7 +482,7 @@ function LocalBlockchain({ account, update, commitments, - proofsEnabled + this.proofsEnabled ); } } @@ -587,7 +587,7 @@ function LocalBlockchain({ // and hopefully with upcoming work by Matt we can just run everything in the prover, and nowhere else let tx = createTransaction(sender, f, 0, { isFinalRunOutsideCircuit: false, - proofsEnabled, + proofsEnabled: this.proofsEnabled, fetchMode: 'test', }); let hasProofs = tx.transaction.accountUpdates.some( @@ -595,7 +595,7 @@ function LocalBlockchain({ ); return createTransaction(sender, f, 1, { isFinalRunOutsideCircuit: !hasProofs, - proofsEnabled, + proofsEnabled: this.proofsEnabled, }); }, applyJsonTransaction(json: string) { @@ -666,7 +666,7 @@ function LocalBlockchain({ networkState.totalCurrency = currency; }, setProofsEnabled(newProofsEnabled: boolean) { - proofsEnabled = newProofsEnabled; + this.proofsEnabled = newProofsEnabled; }, }; } From 7e38cdd56cafab46a8b3741bdf0340fa5153fe51 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 15 Dec 2023 12:24:02 +0100 Subject: [PATCH 1176/1215] label deploy account update --- src/lib/zkapp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 6986966a3d..9346973f72 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -729,7 +729,7 @@ class SmartContract { verificationKey?: { data: string; hash: Field | string }; zkappKey?: PrivateKey; } = {}) { - let accountUpdate = this.newSelf(); + let accountUpdate = this.newSelf('deploy'); verificationKey ??= (this.constructor as typeof SmartContract) ._verificationKey; if (verificationKey === undefined) { @@ -873,10 +873,10 @@ super.init(); /** * Same as `SmartContract.self` but explicitly creates a new {@link AccountUpdate}. */ - newSelf(): AccountUpdate { + newSelf(methodName?: string): AccountUpdate { let inTransaction = Mina.currentTransaction.has(); let transactionId = inTransaction ? Mina.currentTransaction.id() : NaN; - let accountUpdate = selfAccountUpdate(this); + let accountUpdate = selfAccountUpdate(this, methodName); this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } From 673c822c996958d7c3bed5951a352e7609fce9d6 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Fri, 15 Dec 2023 12:49:43 +0100 Subject: [PATCH 1177/1215] remove unused imports --- src/lib/gadgets/arithmetic.unit-test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts index cf1398dadf..075a5d6e32 100644 --- a/src/lib/gadgets/arithmetic.unit-test.ts +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -1,15 +1,12 @@ import { ZkProgram } from '../proof_system.js'; import { - array, equivalentProvable as equivalent, equivalentAsync, field, record, } from '../testing/equivalent.js'; -import { mod } from '../../bindings/crypto/finite_field.js'; import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; -import { Random } from '../testing/property.js'; import { provable } from '../circuit_value.js'; import { assert } from './common.js'; From 519278dc17f4a7a9240197ffd2a0bf0066cb8df2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 15 Dec 2023 14:36:52 +0100 Subject: [PATCH 1178/1215] improve toPretty output of authorization kind --- src/lib/account_update.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 2ab0c5d50d..23f5e66c08 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1505,6 +1505,15 @@ class AccountUpdate implements Types.AccountUpdate { body[key] = JSON.stringify(body[key]) as any; } } + if (body.authorizationKind?.isProved === false) { + delete (body as any).authorizationKind?.verificationKeyHash; + } + if ( + body.authorizationKind?.isProved === false && + body.authorizationKind?.isSigned === false + ) { + delete (body as any).authorizationKind; + } if ( jsonUpdate.authorization !== undefined || body.authorizationKind?.isProved === true || @@ -1512,6 +1521,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { (body as any).authorization = jsonUpdate.authorization; } + body.mayUseToken = { parentsOwnToken: this.body.mayUseToken.parentsOwnToken.toBoolean(), inheritFromParent: this.body.mayUseToken.inheritFromParent.toBoolean(), From 7f0bf7b3b6d0b24ead4ae6b368e27c8febaee89e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 09:02:08 +0100 Subject: [PATCH 1179/1215] move kimchi/{wasm,js} to kimchi_bindings --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 9669d55490..58d78b554d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9669d55490767fffc410e78d6425c7bad4bf4b9f +Subproject commit 58d78b554def18b176e2bc3c45ef11277da3c080 diff --git a/src/mina b/src/mina index d4314b0920..f7a0abc1c9 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit d4314b0920fe06d7ba9f790fb803142da6883570 +Subproject commit f7a0abc1c90bc36a1bcdfe9931fc655852029942 From 8e9fd7c629a576698e7a9039e93b2fa396e60344 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 09:02:39 +0100 Subject: [PATCH 1180/1215] gitignore temp files added during build --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8f62404233..78d26c7d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ profiling.md o1js-reference *.tmp.js _build/ +src/config/ +src/config.mlh From f91e027721e24c8ecc52f981d7598383e33e638e Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Mon, 18 Dec 2023 10:38:42 +0200 Subject: [PATCH 1181/1215] Lightnet README-dev documentation. --- README-dev.md | 54 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/README-dev.md b/README-dev.md index 651359df05..42a570364e 100644 --- a/README-dev.md +++ b/README-dev.md @@ -4,9 +4,9 @@ This README includes information that is helpful for o1js core contributors. ## Setting up the repo on your local -After cloning the repo, you must fetch external submodules for the following examples to work. +After cloning the repo, you must fetch external submodules for the following examples to work. -```sh +```shell git clone https://github.com/o1-labs/o1js.git cd o1js git submodule update --init --recursive @@ -14,7 +14,7 @@ git submodule update --init --recursive ## Run examples using Node.js -```sh +```shell npm install npm run build @@ -23,7 +23,7 @@ npm run build ## Run examples in the browser -```sh +```shell npm install npm run build:web @@ -38,20 +38,20 @@ Note: Some of our examples don't work on the web because they use Node.js APIs. - Unit tests - ```sh + ```shell npm run test npm run test:unit ``` - Integration tests - ```sh + ```shell npm run test:integration ``` - E2E tests - ```sh + ```shell npm install npm run e2e:install npm run build:web @@ -89,8 +89,46 @@ The other base branches (`berkeley`, `develop`) are only used in specific scenar You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: -``` +```shell act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN ``` to execute the job "Build-And-Test-Server for the test type `Simple integration tests`. + +## Test zkApps against the local blockchain network + +In order to be able to test zkApps against the local blockchain network, you need to spin up such a network first. +You can do so in several ways. + +1. Using [zkapp-cli](https://www.npmjs.com/package/zkapp-cli)'s sub commands: + + ```shell + zk lightnet start # start the local network + # Do your tests and other interactions with the network + zk lightnet logs # manage the logs of the local network + zk lightnet explorer # visualize the local network state + zk lightnet stop # stop the local network + ``` + + Please refer to `zk lightnet --help` for more information. + +2. Using the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: + + ```shell + docker run --rm --pull=missing -it \ + --env NETWORK_TYPE="single-node" \ + --env PROOF_LEVEL="none" \ + --env LOG_LEVEL="Trace" \ + -p 3085:3085 \ + -p 5432:5432 \ + -p 8080:8080 \ + -p 8181:8181 \ + -p 8282:8282 \ + o1labs/mina-local-network:o1js-main-latest-lightnet + ``` + + Please refer to the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. + +Next up, you will need the Mina blockchain accounts information in order to be used in your zkApp. +Once the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. +The corresponding example can be found here: [src/examples/zkapps/hello_world/run_live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello_world/run_live.ts) From 72e7707773605f16a112b677acbce5d2ff33caa6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 15:18:52 +0100 Subject: [PATCH 1182/1215] better stack trace limit --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index b82033e1dd..243fe7dbfa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -124,7 +124,7 @@ namespace Experimental { export type Callback = Callback_; } -Error.stackTraceLimit = 1000; +Error.stackTraceLimit = 100000; // deprecated stuff export { isReady, shutdown }; From fd2b78a56a176027855417ab82ce081317e4332a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 15:18:59 +0100 Subject: [PATCH 1183/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 61c75c037d..e218dab5f6 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 61c75c037db058231f4e1b13a4743178b95d0aa0 +Subproject commit e218dab5f6b82957436cf861591a7676c7aea31c From 279f5a2497615d89ace2d9c938c4203d2fe85e57 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 15:39:13 +0100 Subject: [PATCH 1184/1215] add regression test --- src/lib/provable.unit-test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/lib/provable.unit-test.ts diff --git a/src/lib/provable.unit-test.ts b/src/lib/provable.unit-test.ts new file mode 100644 index 0000000000..9c14a0f02c --- /dev/null +++ b/src/lib/provable.unit-test.ts @@ -0,0 +1,23 @@ +import { it } from 'node:test'; +import { Provable } from './provable.js'; +import { exists } from './gadgets/common.js'; +import { Field } from './field.js'; +import { expect } from 'expect'; + +it('can witness large field array', () => { + let N = 100_000; + let arr = Array(N).fill(0n); + + Provable.runAndCheck(() => { + // with exists + let fields = exists(N, () => arr); + + // with Provable.witness + let fields2 = Provable.witness(Provable.Array(Field, N), () => + arr.map(Field.from) + ); + + expect(fields.length).toEqual(N); + expect(fields2.length).toEqual(N); + }); +}); From 5c51bd295ba4966078e86d8baa65f8cdc4a983ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 15:46:51 +0100 Subject: [PATCH 1185/1215] another regression test --- src/lib/proof_system.unit-test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index e1368c0c05..3aab1dc9a5 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -100,6 +100,24 @@ it('pickles rule creation', async () => { ); }); +// compile works with large inputs + +const N = 100_000; + +const program = ZkProgram({ + name: 'large-array-program', + methods: { + baseCase: { + privateInputs: [Provable.Array(Field, N)], + method(_: Field[]) {}, + }, + }, +}); + +it('can compile program with large input', async () => { + await program.compile(); +}); + // regression tests for some zkprograms const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); expect(emptyMethodsMetadata.run).toEqual( From 8b1aa7374d973dbe9003fb5b5fec23f6448e7bf4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 15:50:07 +0100 Subject: [PATCH 1186/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index e218dab5f6..b2b83072d4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e218dab5f6b82957436cf861591a7676c7aea31c +Subproject commit b2b83072d4cedbf0b72fcde245ba53f318ba6e10 From 57de6646462b6d827bc68fab5a16a70c06a8df3e Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 16:00:59 +0100 Subject: [PATCH 1187/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index b2b83072d4..259b8b96fb 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b2b83072d4cedbf0b72fcde245ba53f318ba6e10 +Subproject commit 259b8b96fb5d5772ed41243ec0f1ea3102daefff From 55368de46dadf5eab5be26a40fd52b08d1bac5b2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 16:02:37 +0100 Subject: [PATCH 1188/1215] changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4961b8a2d1..576b115a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Removed_ for now removed features. _Fixed_ for any bug fixes. _Security_ in case of vulnerabilities. - - --> ## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) @@ -25,6 +23,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 - For an example, see `./src/examples/crypto/ecdsa` +### Fixed + +- Fix stack overflows when calling provable methods with large inputs https://github.com/o1-labs/o1js/pull/1334 + ## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) ### Breaking changes From 0d82ad1d67417c72b06fc8456a0a2b54bcc46652 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 17:15:05 +0100 Subject: [PATCH 1189/1215] Merge branch 'main' into feature/glv --- .github/actions/live-tests-shared/action.yml | 16 +- .github/workflows/build-action.yml | 4 +- .github/workflows/release.yml | 73 ++ .prettierignore | 1 - CHANGELOG.md | 30 +- README-dev.md | 66 +- package-lock.json | 17 +- package.json | 14 +- run | 2 +- run-ci-tests.sh | 1 + run-debug | 1 + run-unit-tests.sh | 3 +- src/bindings | 2 +- src/build/copy-to-dist.js | 6 +- src/build/jsLayoutToTypes.mjs | 55 +- src/examples/api_exploration.ts | 4 +- src/examples/benchmarks/foreign-field.ts | 31 + src/examples/crypto/README.md | 6 + src/examples/crypto/ecdsa/ecdsa.ts | 54 ++ src/examples/crypto/ecdsa/run.ts | 41 + src/examples/crypto/foreign-field.ts | 116 +++ .../internals/advanced-provable-types.ts | 2 +- src/examples/zkapps/dex/erc20.ts | 2 +- src/examples/zkapps/hashing/hash.ts | 49 + src/examples/zkapps/hashing/run.ts | 73 ++ src/examples/zkapps/local_events_zkapp.ts | 2 +- src/examples/zkapps/reducer/map.ts | 193 ++++ src/examples/zkapps/sudoku/sudoku.ts | 2 +- src/examples/zkapps/voting/member.ts | 4 +- src/examples/zkapps/voting/preconditions.ts | 2 +- src/examples/zkprogram/ecdsa/ecdsa.ts | 41 - src/examples/zkprogram/ecdsa/run.ts | 43 - src/examples/zkprogram/program-with-input.ts | 2 +- src/examples/zkprogram/program.ts | 2 +- src/index.ts | 16 +- src/lib/account_update.ts | 17 +- src/lib/bool.ts | 52 +- src/lib/circuit.ts | 1 + src/lib/circuit_value.ts | 32 +- src/lib/events.ts | 20 +- src/lib/field.ts | 152 ++-- src/lib/foreign-curve.ts | 312 +++++++ src/lib/foreign-curve.unit-test.ts | 42 + src/lib/foreign-ecdsa.ts | 258 ++++++ src/lib/foreign-field.ts | 739 +++++++++++++++ src/lib/foreign-field.unit-test.ts | 191 ++++ src/lib/gadgets/basic.ts | 10 +- src/lib/gadgets/bitwise.ts | 164 ++-- src/lib/gadgets/common.ts | 16 - src/lib/gadgets/ecdsa.unit-test.ts | 94 +- src/lib/gadgets/elliptic-curve.ts | 187 +++- src/lib/gadgets/elliptic-curve.unit-test.ts | 82 ++ src/lib/gadgets/foreign-field.ts | 105 ++- src/lib/gadgets/foreign-field.unit-test.ts | 30 +- src/lib/gadgets/gadgets.ts | 142 ++- src/lib/gadgets/range-check.ts | 40 +- src/lib/gadgets/range-check.unit-test.ts | 31 +- src/lib/gadgets/test-utils.ts | 21 +- src/lib/gates.ts | 31 +- src/lib/group.ts | 89 +- src/lib/hash-generic.ts | 4 +- src/lib/hash.ts | 21 +- src/lib/hashes-combined.ts | 127 +++ src/lib/int.test.ts | 861 +++++++++++++++++- src/lib/int.ts | 395 +++++++- src/lib/keccak.ts | 550 +++++++++++ src/lib/keccak.unit-test.ts | 267 ++++++ src/lib/mina.ts | 10 +- src/lib/mina/account.ts | 10 +- src/lib/proof_system.ts | 10 +- src/lib/proof_system.unit-test.ts | 114 ++- src/lib/provable-context.ts | 11 +- src/lib/provable-types/bytes.ts | 121 +++ src/lib/provable-types/provable-types.ts | 21 + src/lib/provable.ts | 19 +- src/lib/provable.unit-test.ts | 23 + src/lib/scalar.ts | 38 +- src/lib/signature.ts | 4 +- src/lib/testing/constraint-system.ts | 6 +- src/lib/testing/equivalent.ts | 103 ++- src/lib/testing/random.ts | 53 +- src/lib/testing/testing.unit-test.ts | 15 +- src/lib/util/arrays.ts | 14 + src/lib/util/types.ts | 14 +- src/lib/zkapp.ts | 10 +- src/mina-signer/MinaSigner.ts | 4 +- src/mina-signer/src/memo.ts | 6 +- src/mina-signer/src/sign-zkapp-command.ts | 2 +- .../src/sign-zkapp-command.unit-test.ts | 2 +- src/mina-signer/src/signature.unit-test.ts | 2 +- src/mina-signer/tests/zkapp.unit-test.ts | 2 +- src/provable/curve-bigint.ts | 6 +- src/provable/field-bigint.ts | 6 +- src/provable/poseidon-bigint.ts | 6 +- src/snarky.d.ts | 24 +- src/tests/fake-proof.ts | 96 ++ .../vk-regression/plain-constraint-system.ts | 36 +- tests/vk-regression/vk-regression.json | 58 +- tests/vk-regression/vk-regression.ts | 11 +- update-changelog.sh | 34 + 100 files changed, 6146 insertions(+), 804 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100755 run-debug create mode 100644 src/examples/benchmarks/foreign-field.ts create mode 100644 src/examples/crypto/README.md create mode 100644 src/examples/crypto/ecdsa/ecdsa.ts create mode 100644 src/examples/crypto/ecdsa/run.ts create mode 100644 src/examples/crypto/foreign-field.ts create mode 100644 src/examples/zkapps/hashing/hash.ts create mode 100644 src/examples/zkapps/hashing/run.ts create mode 100644 src/examples/zkapps/reducer/map.ts delete mode 100644 src/examples/zkprogram/ecdsa/ecdsa.ts delete mode 100644 src/examples/zkprogram/ecdsa/run.ts create mode 100644 src/lib/foreign-curve.ts create mode 100644 src/lib/foreign-curve.unit-test.ts create mode 100644 src/lib/foreign-ecdsa.ts create mode 100644 src/lib/foreign-field.ts create mode 100644 src/lib/foreign-field.unit-test.ts create mode 100644 src/lib/gadgets/elliptic-curve.unit-test.ts create mode 100644 src/lib/hashes-combined.ts create mode 100644 src/lib/keccak.ts create mode 100644 src/lib/keccak.unit-test.ts create mode 100644 src/lib/provable-types/bytes.ts create mode 100644 src/lib/provable-types/provable-types.ts create mode 100644 src/lib/provable.unit-test.ts create mode 100644 src/lib/util/arrays.ts create mode 100644 src/tests/fake-proof.ts create mode 100755 update-changelog.sh diff --git a/.github/actions/live-tests-shared/action.yml b/.github/actions/live-tests-shared/action.yml index 05d8970d97..a84bad475a 100644 --- a/.github/actions/live-tests-shared/action.yml +++ b/.github/actions/live-tests-shared/action.yml @@ -1,11 +1,11 @@ -name: "Shared steps for live testing jobs" -description: "Shared steps for live testing jobs" +name: 'Shared steps for live testing jobs' +description: 'Shared steps for live testing jobs' inputs: mina-branch-name: - description: "Mina branch name in use by service container" + description: 'Mina branch name in use by service container' required: true runs: - using: "composite" + using: 'composite' steps: - name: Wait for Mina network readiness uses: o1-labs/wait-for-mina-network-action@v1 @@ -16,15 +16,15 @@ runs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "20" + node-version: '20' - name: Build o1js and execute tests env: - TEST_TYPE: "Live integration tests" - USE_CUSTOM_LOCAL_NETWORK: "true" + TEST_TYPE: 'Live integration tests' + USE_CUSTOM_LOCAL_NETWORK: 'true' run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build touch profiling.md sh run-ci-tests.sh cat profiling.md >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index 3801f8ea78..43cea697ff 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -39,7 +39,7 @@ jobs: run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build touch profiling.md sh run-ci-tests.sh cat profiling.md >> $GITHUB_STEP_SUMMARY @@ -91,7 +91,7 @@ jobs: run: | git submodule update --init --recursive npm ci - npm run build:node + npm run build - name: Publish to NPM if version has changed uses: JS-DevTools/npm-publish@v1 if: github.ref == 'refs/heads/main' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..a3aa4226dd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,73 @@ +# Purpose: +# Automatically bumps the project's patch version bi-weekly on Tuesdays. +# +# Details: +# - Triggered at 00:00 UTC every Tuesday; runs on even weeks of the year. +# - Sets up the environment by checking out the repo and setting up Node.js. +# - Bumps patch version using `npm version patch`, then creates a new branch 'release/x.x.x'. +# - Pushes changes and creates a PR to `main` using GitHub CLI. +# - Can also be triggered manually via `workflow_dispatch`. +name: Version Bump + +on: + workflow_dispatch: # Allow to manually trigger the workflow + schedule: + - cron: "0 0 * * 2" # At 00:00 UTC every Tuesday + +jobs: + version-bump: + runs-on: ubuntu-latest + + steps: + # Since cronjob syntax doesn't support bi-weekly schedule, we need to check if it's an even week or not + - name: Check if it's an even week + run: | + WEEK_NUM=$(date +'%V') + if [ $((WEEK_NUM % 2)) -eq 0 ]; then + echo "RUN_JOB=true" >> $GITHUB_ENV + else + echo "RUN_JOB=false" >> $GITHUB_ENV + fi + + - name: Check out the repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Configure Git + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Bump patch version + if: ${{ env.RUN_JOB }} == 'true' + run: | + git fetch --prune --unshallow + NEW_VERSION=$(npm version patch) + echo "New version: $NEW_VERSION" + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + - name: Install npm dependencies + if: ${{ env.RUN_JOB }} == 'true' + run: npm install + + - name: Update CHANGELOG.md + if: ${{ env.RUN_JOB }} == 'true' + run: | + npm run update-changelog + git add CHANGELOG.md + git commit -m "Update CHANGELOG for new version $NEW_VERSION" + + - name: Create new release branch + if: ${{ env.RUN_JOB }} == 'true' + run: | + NEW_BRANCH="release/${NEW_VERSION}" + git checkout -b $NEW_BRANCH + git push -u origin $NEW_BRANCH + git push --tags + gh pr create --base main --head $NEW_BRANCH --title "Release $NEW_VERSION" --body "This is an automated PR to update to version $NEW_VERSION" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} diff --git a/.prettierignore b/.prettierignore index 099c3fecca..c3f051e6bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,3 @@ src/bindings/compiled/web_bindings/**/plonk_wasm* src/bindings/compiled/web_bindings/**/*.bc.js src/bindings/compiled/node_bindings/* dist/**/* -src/bindings/kimchi/js/**/*.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f361304c1..576b115a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,27 +13,37 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Removed_ for now removed features. _Fixed_ for any bug fixes. _Security_ in case of vulnerabilities. + --> +## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) - --> +### Added + +- Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 +- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 + - For an example, see `./src/examples/crypto/ecdsa` + +### Fixed -## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD) +- Fix stack overflows when calling provable methods with large inputs https://github.com/o1-labs/o1js/pull/1334 -# Breaking changes +## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) -- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1240 +### Breaking changes + +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1292 [@rpanic](https://github.com/rpanic) -# Added +### Added -- **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240 - - For an example, see `./src/examples/zkprogram/ecdsa` +- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 - `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 - `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 - `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 +- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288 ### Changed -- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. +- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. [@LuffySama-Dev](https://github.com/LuffySama-Dev) - `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263 - `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263 - `this.account.x.getAndAssertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265 @@ -41,6 +51,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265 - `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `print()` method for pretty-printing the constraint system https://github.com/o1-labs/o1js/pull/1240 +### Fixed + +- Fix missing recursive verification of proofs in smart contracts https://github.com/o1-labs/o1js/pull/1302 + ## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e) ### Breaking changes diff --git a/README-dev.md b/README-dev.md index e77b02b95d..42a570364e 100644 --- a/README-dev.md +++ b/README-dev.md @@ -2,9 +2,19 @@ This README includes information that is helpful for o1js core contributors. +## Setting up the repo on your local + +After cloning the repo, you must fetch external submodules for the following examples to work. + +```shell +git clone https://github.com/o1-labs/o1js.git +cd o1js +git submodule update --init --recursive +``` + ## Run examples using Node.js -```sh +```shell npm install npm run build @@ -13,7 +23,7 @@ npm run build ## Run examples in the browser -```sh +```shell npm install npm run build:web @@ -28,20 +38,20 @@ Note: Some of our examples don't work on the web because they use Node.js APIs. - Unit tests - ```sh + ```shell npm run test npm run test:unit ``` - Integration tests - ```sh + ```shell npm run test:integration ``` - E2E tests - ```sh + ```shell npm install npm run e2e:install npm run build:web @@ -67,14 +77,58 @@ The following branches are compatible: | | berkeley -> berkeley -> berkeley | | | develop -> develop -> develop | +If you work on o1js, create a feature branch off of one of these base branches. It's encouraged to submit your work-in-progress as a draft PR to raise visibility! + +**Default to `main` as the base branch**. + +The other base branches (`berkeley`, `develop`) are only used in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. + ## Run the GitHub actions locally You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: -``` +```shell act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN ``` to execute the job "Build-And-Test-Server for the test type `Simple integration tests`. + +## Test zkApps against the local blockchain network + +In order to be able to test zkApps against the local blockchain network, you need to spin up such a network first. +You can do so in several ways. + +1. Using [zkapp-cli](https://www.npmjs.com/package/zkapp-cli)'s sub commands: + + ```shell + zk lightnet start # start the local network + # Do your tests and other interactions with the network + zk lightnet logs # manage the logs of the local network + zk lightnet explorer # visualize the local network state + zk lightnet stop # stop the local network + ``` + + Please refer to `zk lightnet --help` for more information. + +2. Using the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: + + ```shell + docker run --rm --pull=missing -it \ + --env NETWORK_TYPE="single-node" \ + --env PROOF_LEVEL="none" \ + --env LOG_LEVEL="Trace" \ + -p 3085:3085 \ + -p 5432:5432 \ + -p 8080:8080 \ + -p 8181:8181 \ + -p 8282:8282 \ + o1labs/mina-local-network:o1js-main-latest-lightnet + ``` + + Please refer to the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. + +Next up, you will need the Mina blockchain accounts information in order to be used in your zkApp. +Once the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. +The corresponding example can be found here: [src/examples/zkapps/hello_world/run_live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello_world/run_live.ts) diff --git a/package-lock.json b/package-lock.json index eff466f3fb..b635120048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.14.2", + "version": "0.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.14.2", + "version": "0.15.0", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", @@ -21,6 +21,7 @@ "snarky-run": "src/build/run.js" }, "devDependencies": { + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", @@ -1486,6 +1487,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 6d880daea0..84665a8f27 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.14.2", + "version": "0.15.0", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ @@ -42,19 +42,17 @@ "node": ">=16.4.0" }, "scripts": { - "dev": "npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js", + "dev": "npx tsc -p tsconfig.test.json && node src/build/copy-to-dist.js", "make": "make -C ../../.. snarkyjs", "make:no-types": "npm run clean && make -C ../../.. snarkyjs_no_types", "wasm": "./src/bindings/scripts/update-wasm-and-types.sh", "bindings": "cd ../../.. && ./scripts/update-snarkyjs-bindings.sh && cd src/lib/snarkyjs", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", - "build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts", - "build:node": "npm run build", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", - "build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0", + "build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", "build:docs": "npx typedoc --tsconfig ./tsconfig.web.json", "prepublish:web": "NODE_ENV=production node src/build/buildWeb.js", - "prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js", + "prepublish:node": "node src/build/copy-artifacts.js && rimraf ./dist/node && npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js && NODE_ENV=production node src/build/buildNode.js", "prepublishOnly": "npm run prepublish:web && npm run prepublish:node", "dump-vks": "./run tests/vk-regression/vk-regression.ts --bundle --dump", "format": "prettier --write --ignore-unknown **/*", @@ -67,10 +65,12 @@ "e2e:prepare-server": "npm run build:examples && (cp -rf dist/examples dist/web || :) && node src/build/e2eTestsBuildHelper.js && cp -rf src/examples/plain-html/index.html src/examples/plain-html/server.js tests/artifacts/html/*.html tests/artifacts/javascript/*.js dist/web", "e2e:run-server": "node dist/web/server.js", "e2e:install": "npx playwright install --with-deps", - "e2e:show-report": "npx playwright show-report tests/report" + "e2e:show-report": "npx playwright show-report tests/report", + "update-changelog": "./update-changelog.sh" }, "author": "O(1) Labs", "devDependencies": { + "@noble/hashes": "^1.3.2", "@playwright/test": "^1.25.2", "@types/isomorphic-fetch": "^0.0.36", "@types/jest": "^27.0.0", diff --git a/run b/run index b039136688..5011793494 100755 --- a/run +++ b/run @@ -1 +1 @@ -node --enable-source-maps --stack-trace-limit=1000 src/build/run.js $@ +node --enable-source-maps src/build/run.js $@ diff --git a/run-ci-tests.sh b/run-ci-tests.sh index cfcdf2e4c0..4b19ebd25d 100755 --- a/run-ci-tests.sh +++ b/run-ci-tests.sh @@ -8,6 +8,7 @@ case $TEST_TYPE in ./run src/examples/simple_zkapp.ts --bundle ./run src/examples/zkapps/reducer/reducer_composite.ts --bundle ./run src/examples/zkapps/composability.ts --bundle + ./run src/tests/fake-proof.ts ;; "Voting integration tests") diff --git a/run-debug b/run-debug new file mode 100755 index 0000000000..05642ff1c3 --- /dev/null +++ b/run-debug @@ -0,0 +1 @@ +node --inspect-brk --enable-source-maps src/build/run.js $@ diff --git a/run-unit-tests.sh b/run-unit-tests.sh index 44881eca5d..3e39307f36 100755 --- a/run-unit-tests.sh +++ b/run-unit-tests.sh @@ -2,8 +2,7 @@ set -e shopt -s globstar # to expand '**' into nested directories./ -# run the build:test -npm run build:test +npm run build # find all unit tests in dist/node and run them # TODO it would be nice to make this work on Mac diff --git a/src/bindings b/src/bindings index 862076ec10..7af5696e11 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 862076ec1079138d20257fad9cb8297f443b2a74 +Subproject commit 7af5696e110243b3a6e1760087384395042710d4 diff --git a/src/build/copy-to-dist.js b/src/build/copy-to-dist.js index 96bc96937b..6218414713 100644 --- a/src/build/copy-to-dist.js +++ b/src/build/copy-to-dist.js @@ -2,7 +2,11 @@ import { copyFromTo } from './utils.js'; await copyFromTo( - ['src/snarky.d.ts', 'src/bindings/compiled/_node_bindings'], + [ + 'src/snarky.d.ts', + 'src/bindings/compiled/_node_bindings', + 'src/bindings/compiled/node_bindings/plonk_wasm.d.cts', + ], 'src/', 'dist/node/' ); diff --git a/src/build/jsLayoutToTypes.mjs b/src/build/jsLayoutToTypes.mjs index 2f0b87caef..d76907da9b 100644 --- a/src/build/jsLayoutToTypes.mjs +++ b/src/build/jsLayoutToTypes.mjs @@ -106,11 +106,22 @@ function writeType(typeData, isJson, withTypeMap) { }; } -function writeTsContent(types, isJson, leavesRelPath) { +function writeTsContent({ + jsLayout: types, + isJson, + isProvable, + leavesRelPath, +}) { let output = ''; let dependencies = new Set(); let converters = {}; let exports = new Set(isJson ? [] : ['customTypes']); + + let fromLayout = isProvable ? 'provableFromLayout' : 'signableFromLayout'; + let FromLayout = isProvable ? 'ProvableFromLayout' : 'SignableFromLayout'; + let GenericType = isProvable ? 'GenericProvableExtended' : 'GenericSignable'; + let GeneratedType = isProvable ? 'ProvableExtended' : 'Signable'; + for (let [Type, value] of Object.entries(types)) { let inner = writeType(value, isJson); exports.add(Type); @@ -118,7 +129,7 @@ function writeTsContent(types, isJson, leavesRelPath) { mergeObject(converters, inner.converters); output += `type ${Type} = ${inner.output};\n\n`; if (!isJson) { - output += `let ${Type} = provableFromLayout<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; + output += `let ${Type} = ${fromLayout}<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; } } @@ -135,8 +146,8 @@ function writeTsContent(types, isJson, leavesRelPath) { import { ${[...imports].join(', ')} } from '${importPath}'; ${ !isJson - ? "import { GenericProvableExtended } from '../../lib/generic.js';\n" + - "import { ProvableFromLayout, GenericLayout } from '../../lib/from-layout.js';\n" + + ? `import { ${GenericType} } from '../../lib/generic.js';\n` + + `import { ${FromLayout}, GenericLayout } from '../../lib/from-layout.js';\n` + "import * as Json from './transaction-json.js';\n" + "import { jsLayout } from './js-layout.js';\n" : '' @@ -147,7 +158,7 @@ ${ !isJson ? 'export { Json };\n' + `export * from '${leavesRelPath}';\n` + - 'export { provableFromLayout, toJSONEssential, emptyValue, Layout, TypeMap };\n' + `export { ${fromLayout}, toJSONEssential, empty, Layout, TypeMap };\n` : `export * from '${leavesRelPath}';\n` + 'export { TypeMap };\n' } @@ -158,7 +169,7 @@ ${ (!isJson || '') && ` const TypeMap: { - [K in keyof TypeMap]: ProvableExtended; + [K in keyof TypeMap]: ${GeneratedType}; } = { ${[...typeMapKeys].join(', ')} } @@ -168,14 +179,14 @@ const TypeMap: { ${ (!isJson || '') && ` -type ProvableExtended = GenericProvableExtended; +type ${GeneratedType} = ${GenericType}; type Layout = GenericLayout; type CustomTypes = { ${customTypes - .map((c) => `${c.typeName}: ProvableExtended<${c.type}, ${c.jsonType}>;`) + .map((c) => `${c.typeName}: ${GeneratedType}<${c.type}, ${c.jsonType}>;`) .join(' ')} } let customTypes: CustomTypes = { ${customTypeNames.join(', ')} }; -let { provableFromLayout, toJSONEssential, emptyValue } = ProvableFromLayout< +let { ${fromLayout}, toJSONEssential, empty } = ${FromLayout}< TypeMap, Json.TypeMap >(TypeMap, customTypes); @@ -196,25 +207,27 @@ async function writeTsFile(content, relPath) { let genPath = '../../bindings/mina-transaction/gen'; await ensureDir(genPath); -let jsonTypesContent = writeTsContent( +let jsonTypesContent = writeTsContent({ jsLayout, - true, - '../transaction-leaves-json.js' -); + isJson: true, + leavesRelPath: '../transaction-leaves-json.js', +}); await writeTsFile(jsonTypesContent, `${genPath}/transaction-json.ts`); -let jsTypesContent = writeTsContent( +let jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves.js' -); + isJson: false, + isProvable: true, + leavesRelPath: '../transaction-leaves.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction.ts`); -jsTypesContent = writeTsContent( +jsTypesContent = writeTsContent({ jsLayout, - false, - '../transaction-leaves-bigint.js' -); + isJson: false, + isProvable: false, + leavesRelPath: '../transaction-leaves-bigint.js', +}); await writeTsFile(jsTypesContent, `${genPath}/transaction-bigint.ts`); await writeTsFile( diff --git a/src/examples/api_exploration.ts b/src/examples/api_exploration.ts index a797656d2c..43e2284950 100644 --- a/src/examples/api_exploration.ts +++ b/src/examples/api_exploration.ts @@ -149,8 +149,8 @@ console.assert(!signature.verify(pubKey, msg1).toBoolean()); */ /* You can initialize elements as literals as follows: */ -let g0 = new Group(-1, 2); -let g1 = new Group({ x: -2, y: 2 }); +let g0 = Group.from(-1, 2); +let g1 = new Group({ x: -1, y: 2 }); /* There is also a predefined generator. */ let g2 = Group.generator; diff --git a/src/examples/benchmarks/foreign-field.ts b/src/examples/benchmarks/foreign-field.ts new file mode 100644 index 0000000000..fb32439e0f --- /dev/null +++ b/src/examples/benchmarks/foreign-field.ts @@ -0,0 +1,31 @@ +import { Crypto, Provable, createForeignField } from 'o1js'; + +class ForeignScalar extends createForeignField( + Crypto.CurveParams.Secp256k1.modulus +) {} + +function main() { + let s = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); + let t = Provable.witness( + ForeignScalar.Canonical.provable, + ForeignScalar.random + ); + s.mul(t); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +console.time('running witness generation & checks'); +Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let cs = Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +console.log(cs.summary()); diff --git a/src/examples/crypto/README.md b/src/examples/crypto/README.md new file mode 100644 index 0000000000..c2f913defa --- /dev/null +++ b/src/examples/crypto/README.md @@ -0,0 +1,6 @@ +# Crypto examples + +These examples show how to use some of the crypto primitives that are supported in provable o1js code. + +- Non-native field arithmetic: `foreign-field.ts` +- Non-native ECDSA verification: `ecdsa.ts` diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts new file mode 100644 index 0000000000..45639c41cb --- /dev/null +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -0,0 +1,54 @@ +import { + ZkProgram, + Crypto, + createEcdsa, + createForeignCurve, + Bool, + Keccak, + Bytes, +} from 'o1js'; + +export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32 }; + +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Scalar extends Secp256k1.Scalar {} +class Ecdsa extends createEcdsa(Secp256k1) {} +class Bytes32 extends Bytes(32) {} + +const keccakAndEcdsa = ZkProgram({ + name: 'ecdsa', + publicInput: Bytes32.provable, + publicOutput: Bool, + + methods: { + verifyEcdsa: { + privateInputs: [Ecdsa.provable, Secp256k1.provable], + method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verify(message, publicKey); + }, + }, + + sha3: { + privateInputs: [], + method(message: Bytes32) { + Keccak.nistSha3(256, message); + return Bool(true); + }, + }, + }, +}); + +const ecdsa = ZkProgram({ + name: 'ecdsa-only', + publicInput: Scalar.provable, + publicOutput: Bool, + + methods: { + verifySignedHash: { + privateInputs: [Ecdsa.provable, Secp256k1.provable], + method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { + return signature.verifySignedHash(message, publicKey); + }, + }, + }, +}); diff --git a/src/examples/crypto/ecdsa/run.ts b/src/examples/crypto/ecdsa/run.ts new file mode 100644 index 0000000000..2a497de373 --- /dev/null +++ b/src/examples/crypto/ecdsa/run.ts @@ -0,0 +1,41 @@ +import { Secp256k1, Ecdsa, keccakAndEcdsa, ecdsa, Bytes32 } from './ecdsa.js'; +import assert from 'assert'; + +// create an example ecdsa signature + +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.generator.scale(privateKey); + +let message = Bytes32.fromString("what's up"); + +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); + +// investigate the constraint system generated by ECDSA verify + +console.time('ecdsa verify only (build constraint system)'); +let csEcdsa = ecdsa.analyzeMethods().verifySignedHash; +console.timeEnd('ecdsa verify only (build constraint system)'); +console.log(csEcdsa.summary()); + +console.time('keccak only (build constraint system)'); +let csKeccak = keccakAndEcdsa.analyzeMethods().sha3; +console.timeEnd('keccak only (build constraint system)'); +console.log(csKeccak.summary()); + +console.time('keccak + ecdsa verify (build constraint system)'); +let cs = keccakAndEcdsa.analyzeMethods().verifyEcdsa; +console.timeEnd('keccak + ecdsa verify (build constraint system)'); +console.log(cs.summary()); + +// compile and prove + +console.time('keccak + ecdsa verify (compile)'); +await keccakAndEcdsa.compile(); +console.timeEnd('keccak + ecdsa verify (compile)'); + +console.time('keccak + ecdsa verify (prove)'); +let proof = await keccakAndEcdsa.verifyEcdsa(message, signature, publicKey); +console.timeEnd('keccak + ecdsa verify (prove)'); + +proof.publicOutput.assertTrue('signature verifies'); +assert(await keccakAndEcdsa.verify(proof), 'proof verifies'); diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts new file mode 100644 index 0000000000..bffdae7654 --- /dev/null +++ b/src/examples/crypto/foreign-field.ts @@ -0,0 +1,116 @@ +/** + * This example explores the ForeignField API! + * + * We shed light on the subtleties of different variants of foreign field: + * Unreduced, AlmostReduced, and Canonical. + */ +import assert from 'assert'; +import { + createForeignField, + AlmostForeignField, + CanonicalForeignField, + Scalar, + SmartContract, + method, + Provable, + state, + State, +} from 'o1js'; + +// Let's create a small finite field: F_17 + +class SmallField extends createForeignField(17n) {} + +let x = SmallField.from(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// most arithmetic operations return "unreduced" fields, i.e., fields that could be larger than the modulus: + +let z = x.add(x); +assert(z instanceof SmallField.Unreduced); + +// note: "unreduced" doesn't usually mean that the underlying witness is larger than the modulus. +// it just means we haven't _proved_ so.. which means a malicious prover _could_ have managed to make it larger. + +// unreduced fields can be added and subtracted, but not be used in multiplcation: + +z.add(1).sub(x).assertEquals(0); // works + +assert((z as any).mul === undefined); // z.mul() is not defined +assert((z as any).inv === undefined); +assert((z as any).div === undefined); + +// to do multiplication, you need "almost reduced" fields: + +let y: AlmostForeignField = z.assertAlmostReduced(); // adds constraints to prove that z is, in fact, reduced +assert(y instanceof SmallField.AlmostReduced); + +y.mul(y).assertEquals(4); // y.mul() is defined +assert(y.mul(y) instanceof SmallField.Unreduced); // but y.mul() returns an unreduced field again + +y.inv().mul(y).assertEquals(1); // y.inv() is defined (and returns an AlmostReduced field!) + +// to do many multiplications, it's more efficient to reduce fields in batches of 3 elements: +// (in fact, asserting that 3 elements are reduced is almost as cheap as asserting that 1 element is reduced) + +let z1 = y.mul(7); +let z2 = y.add(11); +let z3 = y.sub(13); + +let [z1r, z2r, z3r] = SmallField.assertAlmostReduced(z1, z2, z3); + +z1r.mul(z2r); +z2r.div(z3r); + +// here we get to the reason _why_ we have different variants of foreign fields: +// always proving that they are reduced after every operation would be super inefficient! + +// fields created from constants are already reduced -- in fact, they are _fully reduced_ or "canonical": + +let constant: CanonicalForeignField = SmallField.from(1); +assert(constant instanceof SmallField.Canonical); + +SmallField.from(10000n) satisfies CanonicalForeignField; // works because `from()` takes the input mod p +SmallField.from(-1) satisfies CanonicalForeignField; // works because `from()` takes the input mod p + +// canonical fields are a special case of almost reduced fields at the type level: +constant satisfies AlmostForeignField; +constant.mul(constant); + +// the cheapest way to prove that an existing field element is canonical is to show that it is equal to a constant: + +let u = z.add(x); +let uCanonical = u.assertEquals(-3); +assert(uCanonical instanceof SmallField.Canonical); + +// to use the different variants of foreign fields as smart contract inputs, you might want to create a class for them: +class AlmostSmallField extends SmallField.AlmostReduced {} + +class MyContract extends SmartContract { + @state(AlmostSmallField.provable) x = State(); + + @method myMethod(y: AlmostSmallField) { + let x = y.mul(2); + Provable.log(x); + this.x.set(x.assertAlmostReduced()); + } +} +MyContract.analyzeMethods(); // works + +// btw - we support any finite field up to 259 bits. for example, the seqp256k1 base field: +let Fseqp256k1 = createForeignField((1n << 256n) - (1n << 32n) - 0b1111010001n); + +// or the Pallas scalar field, to do arithmetic on scalars: +let Fq = createForeignField(Scalar.ORDER); + +// also, you can use a number that's not a prime. +// for example, you might want to create a UInt256 type: +let UInt256 = createForeignField(1n << 256n); + +// and now you can do arithmetic modulo 2^256! +let a = UInt256.from(1n << 255n); +let b = UInt256.from((1n << 255n) + 7n); +a.add(b).assertEquals(7); + +// have fun proving finite field algorithms! diff --git a/src/examples/internals/advanced-provable-types.ts b/src/examples/internals/advanced-provable-types.ts index 6f7af3a68a..dc3b95b530 100644 --- a/src/examples/internals/advanced-provable-types.ts +++ b/src/examples/internals/advanced-provable-types.ts @@ -58,7 +58,7 @@ expect(accountUpdateRecovered.lazyAuthorization).not.toEqual( /** * Provable.runAndCheck() can be used to run a circuit in "prover mode". * That means - * -) witness() and asProver() blocks are excuted + * -) witness() and asProver() blocks are executed * -) constraints are checked; failing assertions throw an error */ Provable.runAndCheck(() => { diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index 3d0b3a4a25..b18ba43dea 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -193,7 +193,7 @@ class TrivialCoin extends SmartContract implements Erc20 { zkapp.requireSignature(); } - // for letting a zkapp do whatever it wants, as long as no tokens are transfered + // for letting a zkapp do whatever it wants, as long as no tokens are transferred // TODO: atm, we have to restrict the zkapp to have no children // -> need to be able to witness a general layout of account updates @method approveZkapp(callback: Experimental.Callback) { diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts new file mode 100644 index 0000000000..9ad9947dfa --- /dev/null +++ b/src/examples/zkapps/hashing/hash.ts @@ -0,0 +1,49 @@ +import { + Hash, + Field, + SmartContract, + state, + State, + method, + Permissions, + Bytes, +} from 'o1js'; + +let initialCommitment: Field = Field(0); + +export class HashStorage extends SmartContract { + @state(Field) commitment = State(); + + init() { + super.init(); + this.account.permissions.set({ + ...Permissions.default(), + editState: Permissions.proofOrSignature(), + }); + this.commitment.set(initialCommitment); + } + + @method SHA256(xs: Bytes) { + const shaHash = Hash.SHA3_256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method SHA384(xs: Bytes) { + const shaHash = Hash.SHA3_384.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method SHA512(xs: Bytes) { + const shaHash = Hash.SHA3_512.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } + + @method Keccak256(xs: Bytes) { + const shaHash = Hash.Keccak256.hash(xs); + const commitment = Hash.hash(shaHash.toFields()); + this.commitment.set(commitment); + } +} diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts new file mode 100644 index 0000000000..9350211d68 --- /dev/null +++ b/src/examples/zkapps/hashing/run.ts @@ -0,0 +1,73 @@ +import { HashStorage } from './hash.js'; +import { Mina, PrivateKey, AccountUpdate, Bytes } from 'o1js'; + +let txn; +let proofsEnabled = true; +// setup local ledger +let Local = Mina.LocalBlockchain({ proofsEnabled }); +Mina.setActiveInstance(Local); + +if (proofsEnabled) { + console.log('Proofs enabled'); + HashStorage.compile(); +} + +// test accounts that pays all the fees, and puts additional funds into the zkapp +const feePayer = Local.testAccounts[0]; + +// zkapp account +const zkAppPrivateKey = PrivateKey.random(); +const zkAppAddress = zkAppPrivateKey.toPublicKey(); +const zkAppInstance = new HashStorage(zkAppAddress); + +// 0, 1, 2, 3, ..., 32 +const hashData = Bytes.from(Array.from({ length: 32 }, (_, i) => i)); + +console.log('Deploying Hash Example....'); +txn = await Mina.transaction(feePayer.publicKey, () => { + AccountUpdate.fundNewAccount(feePayer.publicKey); + zkAppInstance.deploy(); +}); +await txn.sign([feePayer.privateKey, zkAppPrivateKey]).send(); + +const initialState = + Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); + +let currentState; +console.log('Initial State', initialState); + +console.log(`Updating commitment from ${initialState} using SHA256 ...`); +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA384 ...`); +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA384(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using SHA512 ...`); +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.SHA512(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); + +console.log(`Updating commitment from ${initialState} using Keccak256...`); +txn = await Mina.transaction(feePayer.publicKey, () => { + zkAppInstance.Keccak256(hashData); +}); +await txn.prove(); +await txn.sign([feePayer.privateKey]).send(); +currentState = Mina.getAccount(zkAppAddress).zkapp?.appState?.[0].toString(); +console.log(`Current state successfully updated to ${currentState}`); diff --git a/src/examples/zkapps/local_events_zkapp.ts b/src/examples/zkapps/local_events_zkapp.ts index 5608625689..993ed31e1a 100644 --- a/src/examples/zkapps/local_events_zkapp.ts +++ b/src/examples/zkapps/local_events_zkapp.ts @@ -91,7 +91,7 @@ let events = await zkapp.fetchEvents(UInt32.from(0)); console.log(events); console.log('---- emitted events: ----'); // fetches all events from zkapp starting block height 0 and ending at block height 10 -events = await zkapp.fetchEvents(UInt32.from(0), UInt64.from(10)); +events = await zkapp.fetchEvents(UInt32.from(0), UInt32.from(10)); console.log(events); console.log('---- emitted events: ----'); // fetches all events diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts new file mode 100644 index 0000000000..890555be92 --- /dev/null +++ b/src/examples/zkapps/reducer/map.ts @@ -0,0 +1,193 @@ +import { + Field, + Struct, + method, + PrivateKey, + SmartContract, + Mina, + AccountUpdate, + Reducer, + provable, + PublicKey, + Bool, + Poseidon, + Provable, +} from 'o1js'; + +/* + +This contract emulates a "mapping" data structure, which is a key-value store, similar to a dictionary or hash table or `new Map()` in JavaScript. +In this example, the keys are public keys, and the values are arbitrary field elements. + +This utilizes the `Reducer` as an append online list of actions, which are then looked at to find the value corresponding to a specific key. + + +```ts +// js +const map = new Map(); +map.set(key, value); +map.get(key); + +// contract +zkApp.deploy(); // ... deploy the zkapp +zkApp.set(key, value); // ... set a key-value pair +zkApp.get(key); // ... get a value by key +``` +*/ + +class Option extends Struct({ + isSome: Bool, + value: Field, +}) {} + +const KeyValuePair = provable({ + key: Field, + value: Field, +}); + +class StorageContract extends SmartContract { + reducer = Reducer({ + actionType: KeyValuePair, + }); + + @method set(key: PublicKey, value: Field) { + this.reducer.dispatch({ key: Poseidon.hash(key.toFields()), value }); + } + + @method get(key: PublicKey): Option { + let pendingActions = this.reducer.getActions({ + fromActionState: Reducer.initialActionState, + }); + + let keyHash = Poseidon.hash(key.toFields()); + + let { state: optionValue } = this.reducer.reduce( + pendingActions, + Option, + (state, action) => { + let currentMatch = keyHash.equals(action.key); + return { + isSome: currentMatch.or(state.isSome), + value: Provable.if(currentMatch, action.value, state.value), + }; + }, + { + state: Option.empty(), + actionState: Reducer.initialActionState, + }, + { maxTransactionsWithActions: k } + ); + + return optionValue; + } +} + +let k = 1 << 4; + +let Local = Mina.LocalBlockchain(); +Mina.setActiveInstance(Local); +let cs = StorageContract.analyzeMethods(); + +console.log(`method size for a "mapping" contract with ${k} entries`); +console.log('get rows:', cs['get'].rows); +console.log('set rows:', cs['set'].rows); + +// a test account that pays all the fees +let feePayerKey = Local.testAccounts[0].privateKey; +let feePayer = Local.testAccounts[0].publicKey; + +// the zkapp account +let zkappKey = PrivateKey.random(); +let zkappAddress = zkappKey.toPublicKey(); +let zkapp = new StorageContract(zkappAddress); + +await StorageContract.compile(); + +let tx = await Mina.transaction(feePayer, () => { + AccountUpdate.fundNewAccount(feePayer); + zkapp.deploy(); +}); +await tx.sign([feePayerKey, zkappKey]).send(); + +console.log('deployed'); + +let map: { key: PublicKey; value: Field }[] = [ + { + key: PrivateKey.random().toPublicKey(), + value: Field(192), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(151), + }, + { + key: PrivateKey.random().toPublicKey(), + value: Field(781), + }, +]; + +let key = map[0].key; +let value = map[0].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[1].key; +value = map[1].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[2].key; +value = map[2].value; +console.log(`setting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + zkapp.set(key, value); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +key = map[0].key; +value = map[0].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +let result: any; +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +key = map[1].key; +value = map[1].value; +console.log(`getting key ${key.toBase58()} with value ${value}`); + +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(key); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('found correct match?', result.isSome.toBoolean()); +console.log('matches expected value?', result.value.equals(value).toBoolean()); + +console.log(`getting key invalid key`); +tx = await Mina.transaction(feePayer, () => { + result = zkapp.get(PrivateKey.random().toPublicKey()); +}); +await tx.prove(); +await tx.sign([feePayerKey]).send(); + +console.log('should be isSome(false)', result.isSome.toBoolean()); diff --git a/src/examples/zkapps/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts index 5175d011a1..9af8952ecf 100644 --- a/src/examples/zkapps/sudoku/sudoku.ts +++ b/src/examples/zkapps/sudoku/sudoku.ts @@ -8,7 +8,7 @@ import { isReady, Poseidon, Struct, - Circuit, + Provable, } from 'o1js'; export { Sudoku, SudokuZkApp }; diff --git a/src/examples/zkapps/voting/member.ts b/src/examples/zkapps/voting/member.ts index 1618914b3c..c4b0f6e6cd 100644 --- a/src/examples/zkapps/voting/member.ts +++ b/src/examples/zkapps/voting/member.ts @@ -52,8 +52,8 @@ export class Member extends CircuitValue { return this; } - static empty() { - return new Member(PublicKey.empty(), UInt64.zero); + static empty any>(): InstanceType { + return new Member(PublicKey.empty(), UInt64.zero) as any; } static from(publicKey: PublicKey, balance: UInt64) { diff --git a/src/examples/zkapps/voting/preconditions.ts b/src/examples/zkapps/voting/preconditions.ts index 946ae73d57..c8dccfab59 100644 --- a/src/examples/zkapps/voting/preconditions.ts +++ b/src/examples/zkapps/voting/preconditions.ts @@ -17,7 +17,7 @@ export class ElectionPreconditions { export class ParticipantPreconditions { minMina: UInt64; - maxMina: UInt64; // have to make this "generic" so it applys for both candidate and voter instances + maxMina: UInt64; // have to make this "generic" so it applies for both candidate and voter instances static get default(): ParticipantPreconditions { return new ParticipantPreconditions(UInt64.zero, UInt64.MAXINT()); diff --git a/src/examples/zkprogram/ecdsa/ecdsa.ts b/src/examples/zkprogram/ecdsa/ecdsa.ts deleted file mode 100644 index 183ec3e7f1..0000000000 --- a/src/examples/zkprogram/ecdsa/ecdsa.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Gadgets, ZkProgram, Struct, Crypto } from 'o1js'; - -export { ecdsaProgram, Point, Secp256k1 }; - -let { ForeignField, Field3, Ecdsa } = Gadgets; - -// TODO expose this as part of Gadgets.Curve - -class Point extends Struct({ x: Field3.provable, y: Field3.provable }) { - // point from bigints - static from({ x, y }: { x: bigint; y: bigint }) { - return new Point({ x: Field3.from(x), y: Field3.from(y) }); - } -} - -const Secp256k1 = Crypto.createCurve(Crypto.CurveParams.Secp256k1); - -const ecdsaProgram = ZkProgram({ - name: 'ecdsa', - publicInput: Point, - - methods: { - verifyEcdsa: { - privateInputs: [Ecdsa.Signature.provable, Field3.provable], - method( - publicKey: Point, - signature: Gadgets.Ecdsa.Signature, - msgHash: Gadgets.Field3 - ) { - // assert that private inputs are valid - ForeignField.assertAlmostFieldElements( - [signature.r, signature.s, msgHash], - Secp256k1.order - ); - - // verify signature - Ecdsa.verify(Secp256k1, signature, msgHash, publicKey); - }, - }, - }, -}); diff --git a/src/examples/zkprogram/ecdsa/run.ts b/src/examples/zkprogram/ecdsa/run.ts deleted file mode 100644 index b1d502c7e9..0000000000 --- a/src/examples/zkprogram/ecdsa/run.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Gadgets } from 'o1js'; -import { Point, Secp256k1, ecdsaProgram } from './ecdsa.js'; -import assert from 'assert'; - -// create an example ecdsa signature - -let privateKey = Secp256k1.Scalar.random(); -let publicKey = Secp256k1.scale(Secp256k1.one, privateKey); - -// TODO use an actual keccak hash -let messageHash = Secp256k1.Scalar.random(); - -let signature = Gadgets.Ecdsa.sign(Secp256k1, messageHash, privateKey); - -// investigate the constraint system generated by ECDSA verify - -console.time('ecdsa verify (build constraint system)'); -let cs = ecdsaProgram.analyzeMethods().verifyEcdsa; -console.timeEnd('ecdsa verify (build constraint system)'); - -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} -console.log(gateTypes); - -// compile and prove - -console.time('ecdsa verify (compile)'); -await ecdsaProgram.compile(); -console.timeEnd('ecdsa verify (compile)'); - -console.time('ecdsa verify (prove)'); -let proof = await ecdsaProgram.verifyEcdsa( - Point.from(publicKey), - Gadgets.Ecdsa.Signature.from(signature), - Gadgets.Field3.from(messageHash) -); -console.timeEnd('ecdsa verify (prove)'); - -assert(await ecdsaProgram.verify(proof), 'proof verifies'); diff --git a/src/examples/zkprogram/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts index 005cce1b14..16aab5ab1c 100644 --- a/src/examples/zkprogram/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -42,7 +42,7 @@ console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(Field(0)); diff --git a/src/examples/zkprogram/program.ts b/src/examples/zkprogram/program.ts index 40b5263854..7cc09602b1 100644 --- a/src/examples/zkprogram/program.ts +++ b/src/examples/zkprogram/program.ts @@ -43,7 +43,7 @@ console.log('program digest', MyProgram.digest()); console.log('compiling MyProgram...'); let { verificationKey } = await MyProgram.compile(); -console.log('verification key', verificationKey.slice(0, 10) + '..'); +console.log('verification key', verificationKey.data.slice(0, 10) + '..'); console.log('proving base case...'); let proof = await MyProgram.baseCase(); diff --git a/src/index.ts b/src/index.ts index e47eb9e467..243fe7dbfa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,18 @@ export type { ProvablePure } from './snarky.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; +export { + createForeignField, + ForeignField, + AlmostForeignField, + CanonicalForeignField, +} from './lib/foreign-field.js'; +export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; +export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; export { Poseidon, TokenSymbol } from './lib/hash.js'; +export { Keccak } from './lib/keccak.js'; +export { Hash } from './lib/hashes-combined.js'; + export * from './lib/signature.js'; export type { ProvableExtended, @@ -21,7 +32,8 @@ export { } from './lib/circuit_value.js'; export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; -export { UInt32, UInt64, Int64, Sign } from './lib/int.js'; +export { UInt32, UInt64, Int64, Sign, UInt8 } from './lib/int.js'; +export { Bytes } from './lib/provable-types/provable-types.js'; export { Gadgets } from './lib/gadgets/gadgets.js'; export { Types } from './bindings/mina-transaction/types.js'; @@ -112,7 +124,7 @@ namespace Experimental { export type Callback = Callback_; } -Error.stackTraceLimit = 1000; +Error.stackTraceLimit = 100000; // deprecated stuff export { isReady, shutdown }; diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 292b77d0cb..23f5e66c08 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -445,7 +445,7 @@ const Body = { tokenId?: Field, mayUseToken?: MayUseToken ): Body { - let { body } = Types.AccountUpdate.emptyValue(); + let { body } = Types.AccountUpdate.empty(); body.publicKey = publicKey; if (tokenId) { body.tokenId = tokenId; @@ -463,7 +463,7 @@ const Body = { }, dummy(): Body { - return Types.AccountUpdate.emptyValue().body; + return Types.AccountUpdate.empty().body; }, }; @@ -1277,6 +1277,9 @@ class AccountUpdate implements Types.AccountUpdate { return [{ lazyAuthorization, children, parent, id, label }, aux]; } static toInput = Types.AccountUpdate.toInput; + static empty() { + return AccountUpdate.dummy(); + } static check = Types.AccountUpdate.check; static fromFields(fields: Field[], [other, aux]: any[]): AccountUpdate { let accountUpdate = Types.AccountUpdate.fromFields(fields, aux); @@ -1502,6 +1505,15 @@ class AccountUpdate implements Types.AccountUpdate { body[key] = JSON.stringify(body[key]) as any; } } + if (body.authorizationKind?.isProved === false) { + delete (body as any).authorizationKind?.verificationKeyHash; + } + if ( + body.authorizationKind?.isProved === false && + body.authorizationKind?.isSigned === false + ) { + delete (body as any).authorizationKind; + } if ( jsonUpdate.authorization !== undefined || body.authorizationKind?.isProved === true || @@ -1509,6 +1521,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { (body as any).authorization = jsonUpdate.authorization; } + body.mayUseToken = { parentsOwnToken: this.body.mayUseToken.parentsOwnToken.toBoolean(), inheritFromParent: this.body.mayUseToken.inheritFromParent.toBoolean(), diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 387a4908da..8957e89d53 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -11,7 +11,7 @@ import { defineBinable } from '../bindings/lib/binable.js'; import { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver } from './provable-context.js'; -export { BoolVar, Bool, isBool }; +export { BoolVar, Bool }; // same representation, but use a different name to communicate intent / constraints type BoolVar = FieldVar; @@ -34,7 +34,7 @@ class Bool { value: BoolVar; constructor(x: boolean | Bool | BoolVar) { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { this.value = x.value; return; } @@ -75,7 +75,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() && toBoolean(y)); } - return new Bool(Snarky.bool.and(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.and(this.value, toFieldVar(y))); } /** @@ -87,7 +87,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() || toBoolean(y)); } - return new Bool(Snarky.bool.or(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.or(this.value, toFieldVar(y))); } /** @@ -102,7 +102,7 @@ class Bool { } return; } - Snarky.bool.assertEqual(this.value, Bool.#toVar(y)); + Snarky.bool.assertEqual(this.value, toFieldVar(y)); } catch (err) { throw withMessage(err, message); } @@ -144,7 +144,7 @@ class Bool { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBoolean() === toBoolean(y)); } - return new Bool(Snarky.bool.equals(this.value, Bool.#toVar(y))); + return new Bool(Snarky.bool.equals(this.value, toFieldVar(y))); } /** @@ -194,14 +194,14 @@ class Bool { } static toField(x: Bool | boolean): Field { - return new Field(Bool.#toVar(x)); + return new Field(toFieldVar(x)); } /** * Boolean negation. */ static not(x: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.not(); } return new Bool(!x); @@ -211,7 +211,7 @@ class Bool { * Boolean AND operation. */ static and(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.and(y); } return new Bool(x).and(y); @@ -221,7 +221,7 @@ class Bool { * Boolean OR operation. */ static or(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.or(y); } return new Bool(x).or(y); @@ -231,7 +231,7 @@ class Bool { * Asserts if both {@link Bool} are equal. */ static assertEqual(x: Bool, y: Bool | boolean): void { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { x.assertEquals(y); return; } @@ -242,7 +242,7 @@ class Bool { * Checks two {@link Bool} for equality. */ static equal(x: Bool | boolean, y: Bool | boolean): Bool { - if (Bool.#isBool(x)) { + if (x instanceof Bool) { return x.equals(y); } return new Bool(x).equals(y); @@ -295,6 +295,10 @@ class Bool { return 1; } + static empty() { + return new Bool(false); + } + static toInput(x: Bool): { packed: [Field, number][] } { return { packed: [[x.toField(), 1] as [Field, number]] }; } @@ -314,9 +318,7 @@ class Bool { return BoolBinable.readBytes(bytes, offset); } - static sizeInBytes() { - return 1; - } + static sizeInBytes = 1; static check(x: Bool): void { Snarky.field.assertBoolean(x.value); @@ -340,15 +342,6 @@ class Bool { return new Bool(x.value); }, }; - - static #isBool(x: boolean | Bool | BoolVar): x is Bool { - return x instanceof Bool; - } - - static #toVar(x: boolean | Bool): BoolVar { - if (Bool.#isBool(x)) return x.value; - return FieldVar.constant(B(x)); - } } const BoolBinable = defineBinable({ @@ -360,6 +353,8 @@ const BoolBinable = defineBinable({ }, }); +// internal helper functions + function isConstant(x: boolean | Bool): x is boolean | ConstantBool { if (typeof x === 'boolean') { return true; @@ -368,10 +363,6 @@ function isConstant(x: boolean | Bool): x is boolean | ConstantBool { return x.isConstant(); } -function isBool(x: unknown) { - return x instanceof Bool; -} - function toBoolean(x: boolean | Bool): boolean { if (typeof x === 'boolean') { return x; @@ -379,6 +370,11 @@ function toBoolean(x: boolean | Bool): boolean { return x.toBoolean(); } +function toFieldVar(x: boolean | Bool): BoolVar { + if (x instanceof Bool) return x.value; + return FieldVar.constant(B(x)); +} + // TODO: This is duplicated function withMessage(error: unknown, message?: string) { if (message === undefined || !(error instanceof Error)) return error; diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index 232f3f4f0d..dd697cc967 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import { ProvablePure, Snarky } from '../snarky.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index ad26b94699..d42f267cdf 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -17,6 +17,7 @@ import type { import { Provable } from './provable.js'; import { assert } from './errors.js'; import { inCheckedComputation } from './provable-context.js'; +import { Proof } from './proof_system.js'; // external API export { @@ -40,7 +41,6 @@ export { cloneCircuitValue, circuitValueEquals, toConstant, - isConstant, InferProvable, HashInput, InferJson, @@ -52,6 +52,7 @@ type ProvableExtension = { toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; toJSON: (x: T) => TJson; fromJSON: (x: TJson) => T; + empty: () => T; }; type ProvableExtended = Provable & @@ -249,6 +250,15 @@ abstract class CircuitValue { } return Object.assign(Object.create(this.prototype), props); } + + static empty(): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields ?? []; + let props: any = {}; + fields.forEach(([key, propType]) => { + props[key] = propType.empty(); + }); + return Object.assign(Object.create(this.prototype), props); + } } function prop(this: any, target: any, key: string) { @@ -377,6 +387,7 @@ function Struct< }; toJSON: (x: T) => J; fromJSON: (x: J) => T; + empty: () => T; } { class Struct_ { static type = provable(type); @@ -434,6 +445,15 @@ function Struct< let struct = Object.create(this.prototype); return Object.assign(struct, value); } + /** + * Create an instance of this struct filled with default values + * @returns an empty instance of this struct + */ + static empty(): T { + let value = this.type.empty(); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } /** * This method is for internal use, you will probably not need it. * Method to make assertions which should be always made whenever a struct of this type is created in a proof. @@ -577,10 +597,13 @@ function cloneCircuitValue(obj: T): T { ) as any as T; if (ArrayBuffer.isView(obj)) return new (obj.constructor as any)(obj); - // o1js primitives aren't cloned + // o1js primitives and proofs aren't cloned if (isPrimitive(obj)) { return obj; } + if (obj instanceof Proof) { + return obj; + } // cloning strategy that works for plain objects AND classes whose constructor only assigns properties let propertyDescriptors: Record = {}; @@ -669,8 +692,3 @@ function toConstant(type: Provable, value: T): T { type.toAuxiliary(value) ); } - -function isConstant(type: FlexibleProvable, value: T): boolean; -function isConstant(type: Provable, value: T): boolean { - return type.toFields(value).every((x) => x.isConstant()); -} diff --git a/src/lib/events.ts b/src/lib/events.ts index 3e92a30389..70fce76400 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -1,8 +1,8 @@ import { prefixes } from '../bindings/crypto/constants.js'; import { prefixToField } from '../bindings/lib/binable.js'; import { - GenericField, GenericProvableExtended, + GenericSignableField, } from '../bindings/lib/generic.js'; export { createEvents, dataAsHash }; @@ -15,7 +15,7 @@ function createEvents({ Field, Poseidon, }: { - Field: GenericField; + Field: GenericSignableField; Poseidon: Poseidon; }) { type Event = Field[]; @@ -60,7 +60,7 @@ function createEvents({ const EventsProvable = { ...Events, ...dataAsHash({ - emptyValue: Events.empty, + empty: Events.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -107,7 +107,7 @@ function createEvents({ const SequenceEventsProvable = { ...Actions, ...dataAsHash({ - emptyValue: Actions.empty, + empty: Actions.empty, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -123,18 +123,16 @@ function createEvents({ } function dataAsHash({ - emptyValue, + empty, toJSON, fromJSON, }: { - emptyValue: () => { data: T; hash: Field }; + empty: () => { data: T; hash: Field }; toJSON: (value: T) => J; fromJSON: (json: J) => { data: T; hash: Field }; -}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> & { - emptyValue(): { data: T; hash: Field }; -} { +}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> { return { - emptyValue, + empty, sizeInFields() { return 1; }, @@ -142,7 +140,7 @@ function dataAsHash({ return [hash]; }, toAuxiliary(value) { - return [value?.data ?? emptyValue().data]; + return [value?.data ?? empty().data]; }, fromFields([hash], [data]) { return { data, hash }; diff --git a/src/lib/field.ts b/src/lib/field.ts index 9e2497e231..891390619d 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -17,11 +17,11 @@ export { ConstantField, VarField, VarFieldVar, - isField, withMessage, readVarMessage, toConstantField, toFp, + checkBitLength, }; type FieldConst = [0, bigint]; @@ -155,7 +155,7 @@ class Field { * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a Field. */ constructor(x: bigint | number | string | Field | FieldVar | FieldConst) { - if (Field.#isField(x)) { + if (x instanceof Field) { this.value = x.value; return; } @@ -175,21 +175,9 @@ class Field { } // helpers - static #isField( - x: bigint | number | string | Field | FieldVar | FieldConst - ): x is Field { - return x instanceof Field; - } - static #toConst(x: bigint | number | string | ConstantField): FieldConst { - if (Field.#isField(x)) return x.value[1]; - return FieldConst.fromBigint(Fp(x)); - } - static #toVar(x: bigint | number | string | Field): FieldVar { - if (Field.#isField(x)) return x.value; - return FieldVar.constant(Fp(x)); - } + static from(x: bigint | number | string | Field): Field { - if (Field.#isField(x)) return x; + if (x instanceof Field) return x; return new Field(x); } @@ -215,10 +203,6 @@ class Field { return this.value[0] === FieldType.Constant; } - #toConstant(name: string): ConstantField { - return toConstantField(this, name, 'x', 'field element'); - } - /** * Create a {@link Field} element equivalent to this {@link Field} element's value, * but is a constant. @@ -233,7 +217,7 @@ class Field { * @return A constant {@link Field} element equivalent to this {@link Field} element. */ toConstant(): ConstantField { - return this.#toConstant('toConstant'); + return toConstant(this, 'toConstant'); } /** @@ -250,7 +234,7 @@ class Field { * @return A bigint equivalent to the bigint representation of the Field. */ toBigInt() { - let x = this.#toConstant('toBigInt'); + let x = toConstant(this, 'toBigInt'); return FieldConst.toBigint(x.value[1]); } @@ -268,7 +252,7 @@ class Field { * @return A string equivalent to the string representation of the Field. */ toString() { - return this.#toConstant('toString').toBigInt().toString(); + return toConstant(this, 'toString').toBigInt().toString(); } /** @@ -289,7 +273,7 @@ class Field { } return; } - Snarky.field.assertEqual(this.value, Field.#toVar(y)); + Snarky.field.assertEqual(this.value, toFieldVar(y)); } catch (err) { throw withMessage(err, message); } @@ -328,7 +312,7 @@ class Field { return new Field(Fp.add(this.toBigInt(), toFp(y))); } // return new AST node Add(x, y) - let z = Snarky.field.add(this.value, Field.#toVar(y)); + let z = Snarky.field.add(this.value, toFieldVar(y)); return new Field(z); } @@ -455,7 +439,7 @@ class Field { } // if one of the factors is constant, return Scale AST node if (isConstant(y)) { - let z = Snarky.field.scale(Field.#toConst(y), this.value); + let z = Snarky.field.scale(toFieldConst(y), this.value); return new Field(z); } if (this.isConstant()) { @@ -663,24 +647,6 @@ class Field { return new Field(xMinusY).isZero(); } - // internal base method for all comparisons - #compare(y: FieldVar) { - // TODO: support all bit lengths - let maxLength = Fp.sizeInBits - 2; - asProver(() => { - let actualLength = Math.max( - this.toBigInt().toString(2).length, - new Field(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.` - ); - }); - let [, less, lessOrEqual] = Snarky.field.compare(maxLength, this.value, y); - return { less: new Bool(less), lessOrEqual: new Bool(lessOrEqual) }; - } - /** * Check if this {@link Field} is less than another "field-like" value. * Returns a {@link Bool}, which is a provable type and can be used prove to the validity of this statement. @@ -708,7 +674,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() < toFp(y)); } - return this.#compare(Field.#toVar(y)).less; + return compare(this, toFieldVar(y)).less; } /** @@ -738,7 +704,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() <= toFp(y)); } - return this.#compare(Field.#toVar(y)).lessOrEqual; + return compare(this, toFieldVar(y)).lessOrEqual; } /** @@ -816,7 +782,7 @@ class Field { } return; } - let { less } = this.#compare(Field.#toVar(y)); + let { less } = compare(this, toFieldVar(y)); less.assertTrue(); } catch (err) { throw withMessage(err, message); @@ -844,7 +810,7 @@ class Field { } return; } - let { lessOrEqual } = this.#compare(Field.#toVar(y)); + let { lessOrEqual } = compare(this, toFieldVar(y)); lessOrEqual.assertTrue(); } catch (err) { throw withMessage(err, message); @@ -938,15 +904,6 @@ class Field { } } - static #checkBitLength(name: string, length: number) { - if (length > Fp.sizeInBits) - throw Error( - `${name}: bit length must be ${Fp.sizeInBits} or less, got ${length}` - ); - if (length <= 0) - throw Error(`${name}: bit length must be positive, got ${length}`); - } - /** * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. * @@ -961,7 +918,7 @@ class Field { * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ toBits(length?: number) { - if (length !== undefined) Field.#checkBitLength('Field.toBits()', length); + if (length !== undefined) checkBitLength('Field.toBits()', length); if (this.isConstant()) { let bits = Fp.toBits(this.toBigInt()); if (length !== undefined) { @@ -988,7 +945,7 @@ class Field { */ static fromBits(bits: (Bool | boolean)[]) { let length = bits.length; - Field.#checkBitLength('Field.fromBits()', length); + checkBitLength('Field.fromBits()', length); if (bits.every((b) => typeof b === 'boolean' || b.toField().isConstant())) { let bits_ = bits .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) @@ -1016,7 +973,7 @@ class Field { * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. */ rangeCheckHelper(length: number) { - Field.#checkBitLength('Field.rangeCheckHelper()', length); + checkBitLength('Field.rangeCheckHelper()', length); if (length % 16 !== 0) throw Error( 'Field.rangeCheckHelper(): `length` has to be a multiple of 16.' @@ -1155,6 +1112,10 @@ class Field { // ProvableExtended + static empty() { + return new Field(0n); + } + /** * Serialize the {@link Field} to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. * @@ -1169,7 +1130,7 @@ class Field { * @return A string equivalent to the JSON representation of the {@link Field}. */ toJSON() { - return this.#toConstant('toJSON').toString(); + return toConstant(this, 'toJSON').toString(); } /** @@ -1260,26 +1221,14 @@ class Field { } /** - * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * - * As all {@link Field} elements have 32 bytes, this function returns 32. - * - * @return The size of a {@link Field} element - 32. + * The size of a {@link Field} element in bytes - 32. */ - static sizeInBytes() { - return Fp.sizeInBytes(); - } + static sizeInBytes = Fp.sizeInBytes; /** - * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * - * As all {@link Field} elements have 255 bits, this function returns 255. - * - * @return The size of a {@link Field} element in bits - 255. + * The size of a {@link Field} element in bits - 255. */ - static sizeInBits() { - return Fp.sizeInBits; - } + static sizeInBits = Fp.sizeInBits; } const FieldBinable = defineBinable({ @@ -1295,9 +1244,7 @@ const FieldBinable = defineBinable({ }, }); -function isField(x: unknown): x is Field { - return x instanceof Field; -} +// internal helper functions function isConstant( x: bigint | number | string | Field @@ -1317,12 +1264,57 @@ function toFp(x: bigint | number | string | Field): Fp { return (x as Field).toBigInt(); } +function toFieldConst(x: bigint | number | string | ConstantField): FieldConst { + if (x instanceof Field) return x.value[1]; + return FieldConst.fromBigint(Fp(x)); +} + +function toFieldVar(x: bigint | number | string | Field): FieldVar { + if (x instanceof Field) return x.value; + return FieldVar.constant(Fp(x)); +} + function withMessage(error: unknown, message?: string) { if (message === undefined || !(error instanceof Error)) return error; error.message = `${message}\n${error.message}`; return error; } +// internal base method for all comparisons +function compare(x: Field, y: FieldVar) { + // TODO: support all bit lengths + let maxLength = Fp.sizeInBits - 2; + asProver(() => { + let actualLength = Math.max( + x.toBigInt().toString(2).length, + new Field(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.` + ); + }); + let [, less, lessOrEqual] = Snarky.field.compare(maxLength, x.value, y); + return { less: new Bool(less), lessOrEqual: new Bool(lessOrEqual) }; +} + +function checkBitLength( + name: string, + length: number, + maxLength = Fp.sizeInBits +) { + if (length > maxLength) + throw Error( + `${name}: bit length must be ${maxLength} or less, got ${length}` + ); + if (length < 0) + throw Error(`${name}: bit length must be non-negative, got ${length}`); +} + +function toConstant(x: Field, name: string): ConstantField { + return toConstantField(x, name, 'x', 'field element'); +} + function toConstantField( x: Field, methodName: string, diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts new file mode 100644 index 0000000000..08fb733bfd --- /dev/null +++ b/src/lib/foreign-curve.ts @@ -0,0 +1,312 @@ +import { + CurveParams, + CurveAffine, + createCurveAffine, +} from '../bindings/crypto/elliptic_curve.js'; +import type { Group } from './group.js'; +import { ProvablePureExtended } from './circuit_value.js'; +import { AlmostForeignField, createForeignField } from './foreign-field.js'; +import { EllipticCurve, Point } from './gadgets/elliptic-curve.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { assert } from './gadgets/common.js'; +import { Provable } from './provable.js'; +import { provableFromClass } from '../bindings/lib/provable-snarky.js'; + +// external API +export { createForeignCurve, ForeignCurve }; + +// internal API +export { toPoint, FlexiblePoint }; + +type FlexiblePoint = { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; +}; +function toPoint({ x, y }: ForeignCurve): Point { + return { x: x.value, y: y.value }; +} + +class ForeignCurve { + x: AlmostForeignField; + y: AlmostForeignField; + + /** + * Create a new {@link ForeignCurve} from an object representing the (affine) x and y coordinates. + * + * @example + * ```ts + * let x = new ForeignCurve({ x: 1n, y: 1n }); + * ``` + * + * **Important**: By design, there is no way for a `ForeignCurve` to represent the zero point. + * + * **Warning**: This fails for a constant input which does not represent an actual point on the curve. + */ + constructor(g: { + x: AlmostForeignField | Field3 | bigint | number; + y: AlmostForeignField | Field3 | bigint | number; + }) { + this.x = new this.Constructor.Field(g.x); + this.y = new this.Constructor.Field(g.y); + // don't allow constants that aren't on the curve + if (this.isConstant()) { + this.assertOnCurve(); + this.assertInSubgroup(); + } + } + + /** + * Coerce the input to a {@link ForeignCurve}. + */ + static from(g: ForeignCurve | FlexiblePoint) { + if (g instanceof this) return g; + return new this(g); + } + + /** + * The constant generator point. + */ + static get generator() { + return new this(this.Bigint.one); + } + /** + * The size of the curve's base field. + */ + static get modulus() { + return this.Bigint.modulus; + } + /** + * The size of the curve's base field. + */ + get modulus() { + return this.Constructor.Bigint.modulus; + } + + /** + * Checks whether this curve point is constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Provable.isConstant(this.Constructor.provable, this); + } + + /** + * Convert this curve point to a point with bigint coordinates. + */ + toBigint() { + return this.Constructor.Bigint.fromNonzero({ + x: this.x.toBigInt(), + y: this.y.toBigInt(), + }); + } + + /** + * Elliptic curve addition. + * + * ```ts + * let r = p.add(q); // r = p + q + * ``` + * + * **Important**: this is _incomplete addition_ and does not handle the degenerate cases: + * - Inputs are equal, `g = h` (where you would use {@link double}). + * In this case, the result of this method is garbage and can be manipulated arbitrarily by a malicious prover. + * - Inputs are inverses of each other, `g = -h`, so that the result would be the zero point. + * In this case, the proof fails. + * + * If you want guaranteed soundness regardless of the input, use {@link addSafe} instead. + * + * @throws if the inputs are inverses of each other. + */ + add(h: ForeignCurve | FlexiblePoint) { + let Curve = this.Constructor.Bigint; + let h_ = this.Constructor.from(h); + let p = EllipticCurve.add(toPoint(this), toPoint(h_), Curve); + return new this.Constructor(p); + } + + /** + * Safe elliptic curve addition. + * + * This is the same as {@link add}, but additionally proves that the inputs are not equal. + * Therefore, the method is guaranteed to either fail or return a valid addition result. + * + * **Beware**: this is more expensive than {@link add}, and is still incomplete in that + * it does not succeed on equal or inverse inputs. + * + * @throws if the inputs are equal or inverses of each other. + */ + addSafe(h: ForeignCurve | FlexiblePoint) { + let h_ = this.Constructor.from(h); + + // prove that we have x1 != x2 => g != +-h + let x1 = this.x.assertCanonical(); + let x2 = h_.x.assertCanonical(); + x1.equals(x2).assertFalse(); + + return this.add(h_); + } + + /** + * Elliptic curve doubling. + * + * @example + * ```ts + * let r = p.double(); // r = 2 * p + * ``` + */ + double() { + let Curve = this.Constructor.Bigint; + let p = EllipticCurve.double(toPoint(this), Curve); + return new this.Constructor(p); + } + + /** + * Elliptic curve negation. + * + * @example + * ```ts + * let r = p.negate(); // r = -p + * ``` + */ + negate(): ForeignCurve { + return new this.Constructor({ x: this.x, y: this.y.neg() }); + } + + /** + * Elliptic curve scalar multiplication, where the scalar is represented as a {@link ForeignField} element. + * + * **Important**: this proves that the result of the scalar multiplication is not the zero point. + * + * @throws if the scalar multiplication results in the zero point; for example, if the scalar is zero. + * + * @example + * ```ts + * let r = p.scale(s); // r = s * p + * ``` + */ + scale(scalar: AlmostForeignField | bigint | number) { + let Curve = this.Constructor.Bigint; + let scalar_ = this.Constructor.Scalar.from(scalar); + let p = EllipticCurve.scale(scalar_.value, toPoint(this), Curve); + return new this.Constructor(p); + } + + static assertOnCurve(g: ForeignCurve) { + EllipticCurve.assertOnCurve(toPoint(g), this.Bigint); + } + + /** + * Assert that this point lies on the elliptic curve, which means it satisfies the equation + * `y^2 = x^3 + ax + b` + */ + assertOnCurve() { + this.Constructor.assertOnCurve(this); + } + + static assertInSubgroup(g: ForeignCurve) { + if (this.Bigint.hasCofactor) { + EllipticCurve.assertInSubgroup(toPoint(g), this.Bigint); + } + } + + /** + * Assert that this point lies in the subgroup defined by `order*P = 0`. + * + * Note: this is a no-op if the curve has cofactor equal to 1. Otherwise + * it performs the full scalar multiplication `order*P` and is expensive. + */ + assertInSubgroup() { + this.Constructor.assertInSubgroup(this); + } + + /** + * Check that this is a valid element of the target subgroup of the curve: + * - Check that the coordinates are valid field elements + * - Use {@link assertOnCurve()} to check that the point lies on the curve + * - If the curve has cofactor unequal to 1, use {@link assertInSubgroup()}. + */ + static check(g: ForeignCurve) { + // more efficient than the automatic check, which would do this for each field separately + this.Field.assertAlmostReduced(g.x, g.y); + this.assertOnCurve(g); + this.assertInSubgroup(g); + } + + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof ForeignCurve; + } + static _Bigint?: CurveAffine; + static _Field?: typeof AlmostForeignField; + static _Scalar?: typeof AlmostForeignField; + static _provable?: ProvablePureExtended< + ForeignCurve, + { x: string; y: string } + >; + + /** + * Curve arithmetic on JS bigints. + */ + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignCurve not initialized'); + return this._Bigint; + } + /** + * The base field of this curve as a {@link ForeignField}. + */ + static get Field() { + assert(this._Field !== undefined, 'ForeignCurve not initialized'); + return this._Field; + } + /** + * The scalar field of this curve as a {@link ForeignField}. + */ + static get Scalar() { + assert(this._Scalar !== undefined, 'ForeignCurve not initialized'); + return this._Scalar; + } + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'ForeignCurve not initialized'); + return this._provable; + } +} + +/** + * Create a class representing an elliptic curve group, which is different from the native {@link Group}. + * + * ```ts + * const Curve = createForeignCurve(Crypto.CurveParams.Secp256k1); + * ``` + * + * `createForeignCurve(params)` takes curve parameters {@link CurveParams} as input. + * We support `modulus` and `order` to be prime numbers up to 259 bits. + * + * The returned {@link ForeignCurve} class represents a _non-zero curve point_ and supports standard + * elliptic curve operations like point addition and scalar multiplication. + * + * {@link ForeignCurve} also includes to associated foreign fields: `ForeignCurve.Field` and `ForeignCurve.Scalar`, see {@link createForeignField}. + */ +function createForeignCurve(params: CurveParams): typeof ForeignCurve { + const FieldUnreduced = createForeignField(params.modulus); + const ScalarUnreduced = createForeignField(params.order); + class Field extends FieldUnreduced.AlmostReduced {} + class Scalar extends ScalarUnreduced.AlmostReduced {} + + const BigintCurve = createCurveAffine(params); + + class Curve extends ForeignCurve { + static _Bigint = BigintCurve; + static _Field = Field; + static _Scalar = Scalar; + static _provable = provableFromClass(Curve, { + x: Field.provable, + y: Field.provable, + }); + } + + return Curve; +} diff --git a/src/lib/foreign-curve.unit-test.ts b/src/lib/foreign-curve.unit-test.ts new file mode 100644 index 0000000000..9cdac03730 --- /dev/null +++ b/src/lib/foreign-curve.unit-test.ts @@ -0,0 +1,42 @@ +import { createForeignCurve } from './foreign-curve.js'; +import { Fq } from '../bindings/crypto/finite_field.js'; +import { Vesta as V } from '../bindings/crypto/elliptic_curve.js'; +import { Provable } from './provable.js'; +import { Field } from './field.js'; +import { Crypto } from './crypto.js'; + +class Vesta extends createForeignCurve(Crypto.CurveParams.Vesta) {} +class Fp extends Vesta.Scalar {} + +let g = { x: Fq.negate(1n), y: 2n, infinity: false }; +let h = V.toAffine(V.negate(V.double(V.add(V.fromAffine(g), V.one)))); +let scalar = Field.random().toBigInt(); +let p = V.toAffine(V.scale(V.fromAffine(h), scalar)); + +function main() { + let g0 = Provable.witness(Vesta.provable, () => new Vesta(g)); + let one = Provable.witness(Vesta.provable, () => Vesta.generator); + let h0 = g0.add(one).double().negate(); + Provable.assertEqual(Vesta.provable, h0, new Vesta(h)); + + h0.assertOnCurve(); + h0.assertInSubgroup(); + + let scalar0 = Provable.witness(Fp.provable, () => new Fp(scalar)); + let p0 = h0.scale(scalar0); + Provable.assertEqual(Vesta.provable, p0, new Vesta(p)); +} + +console.time('running constant version'); +main(); +console.timeEnd('running constant version'); + +console.time('running witness generation & checks'); +Provable.runAndCheck(main); +console.timeEnd('running witness generation & checks'); + +console.time('creating constraint system'); +let cs = Provable.constraintSystem(main); +console.timeEnd('creating constraint system'); + +console.log(cs.summary()); diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts new file mode 100644 index 0000000000..bccbaa77ab --- /dev/null +++ b/src/lib/foreign-ecdsa.ts @@ -0,0 +1,258 @@ +import { provableFromClass } from '../bindings/lib/provable-snarky.js'; +import { CurveParams } from '../bindings/crypto/elliptic_curve.js'; +import { ProvablePureExtended } from './circuit_value.js'; +import { + FlexiblePoint, + ForeignCurve, + createForeignCurve, + toPoint, +} from './foreign-curve.js'; +import { AlmostForeignField } from './foreign-field.js'; +import { assert } from './gadgets/common.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { Ecdsa } from './gadgets/elliptic-curve.js'; +import { l } from './gadgets/range-check.js'; +import { Keccak } from './keccak.js'; +import { Bytes } from './provable-types/provable-types.js'; +import { UInt8 } from './int.js'; + +// external API +export { createEcdsa, EcdsaSignature }; + +type FlexibleSignature = + | EcdsaSignature + | { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }; + +class EcdsaSignature { + r: AlmostForeignField; + s: AlmostForeignField; + + /** + * Create a new {@link EcdsaSignature} from an object containing the scalars r and s. + * @param signature + */ + constructor(signature: { + r: AlmostForeignField | Field3 | bigint | number; + s: AlmostForeignField | Field3 | bigint | number; + }) { + this.r = new this.Constructor.Curve.Scalar(signature.r); + this.s = new this.Constructor.Curve.Scalar(signature.s); + } + + /** + * Coerce the input to a {@link EcdsaSignature}. + */ + static from(signature: FlexibleSignature): EcdsaSignature { + if (signature instanceof this) return signature; + return new this(signature); + } + + /** + * Create an {@link EcdsaSignature} from a raw 130-char hex string as used in + * [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope). + */ + static fromHex(rawSignature: string): EcdsaSignature { + let s = Ecdsa.Signature.fromHex(rawSignature); + return new this(s); + } + + /** + * Convert this signature to an object with bigint fields. + */ + toBigInt() { + return { r: this.r.toBigInt(), s: this.s.toBigInt() }; + } + + /** + * Verify the ECDSA signature given the message (an array of bytes) and public key (a {@link Curve} point). + * + * **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. + * So, to actually prove validity of a signature, you need to assert that the result is true. + * + * @throws if one of the signature scalars is zero or if the public key is not on the curve. + * + * @example + * ```ts + * // create classes for your curve + * class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} + * class Scalar extends Secp256k1.Scalar {} + * class Ecdsa extends createEcdsa(Secp256k1) {} + * + * let message = 'my message'; + * let messageBytes = new TextEncoder().encode(message); + * + * // outside provable code: create inputs + * let privateKey = Scalar.random(); + * let publicKey = Secp256k1.generator.scale(privateKey); + * let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); + * + * // ... + * // in provable code: create input witnesses (or use method inputs, or constants) + * let pk = Provable.witness(Secp256k1.provable, () => publicKey); + * let msg = Provable.witness(Provable.Array(Field, 9), () => messageBytes.map(Field)); + * let sig = Provable.witness(Ecdsa.provable, () => signature); + * + * // verify signature + * let isValid = sig.verify(msg, pk); + * isValid.assertTrue('signature verifies'); + * ``` + */ + verify(message: Bytes, publicKey: FlexiblePoint) { + let msgHashBytes = Keccak.ethereum(message); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); + return this.verifySignedHash(msgHash, publicKey); + } + + /** + * Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). + * + * This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + */ + verifySignedHash( + msgHash: AlmostForeignField | bigint, + publicKey: FlexiblePoint + ) { + let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); + let publicKey_ = this.Constructor.Curve.from(publicKey); + return Ecdsa.verify( + this.Constructor.Curve.Bigint, + toObject(this), + msgHash_.value, + toPoint(publicKey_) + ); + } + + /** + * Create an {@link EcdsaSignature} by signing a message with a private key. + * + * Note: This method is not provable, and only takes JS bigints as input. + */ + static sign(message: (bigint | number)[] | Uint8Array, privateKey: bigint) { + let msgHashBytes = Keccak.ethereum(message); + let msgHash = keccakOutputToScalar(msgHashBytes, this.Curve); + return this.signHash(msgHash.toBigInt(), privateKey); + } + + /** + * Create an {@link EcdsaSignature} by signing a message hash with a private key. + * + * This is a building block of {@link EcdsaSignature.sign}, where the input message is also hashed. + * In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in + * choosing the hashing algorithm. + * + * Note: This method is not provable, and only takes JS bigints as input. + */ + static signHash(msgHash: bigint, privateKey: bigint) { + let { r, s } = Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey); + return new this({ r, s }); + } + + static check(signature: EcdsaSignature) { + // more efficient than the automatic check, which would do this for each scalar separately + this.Curve.Scalar.assertAlmostReduced(signature.r, signature.s); + } + + // dynamic subclassing infra + get Constructor() { + return this.constructor as typeof EcdsaSignature; + } + static _Curve?: typeof ForeignCurve; + static _provable?: ProvablePureExtended< + EcdsaSignature, + { r: string; s: string } + >; + + /** + * The {@link ForeignCurve} on which the ECDSA signature is defined. + */ + static get Curve() { + assert(this._Curve !== undefined, 'EcdsaSignature not initialized'); + return this._Curve; + } + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'EcdsaSignature not initialized'); + return this._provable; + } +} + +/** + * Create a class {@link EcdsaSignature} for verifying ECDSA signatures on the given curve. + */ +function createEcdsa( + curve: CurveParams | typeof ForeignCurve +): typeof EcdsaSignature { + let Curve0: typeof ForeignCurve = + 'b' in curve ? createForeignCurve(curve) : curve; + class Curve extends Curve0 {} + + class Signature extends EcdsaSignature { + static _Curve = Curve; + static _provable = provableFromClass(Signature, { + r: Curve.Scalar.provable, + s: Curve.Scalar.provable, + }); + } + + return Signature; +} + +function toObject(signature: EcdsaSignature) { + return { r: signature.r.value, s: signature.s.value }; +} + +/** + * Provable method to convert keccak256 hash output to ECDSA scalar = "message hash" + * + * Spec from [Wikipedia](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm): + * + * > Let z be the L_n leftmost bits of e, where L_{n} is the bit length of the group order n. + * > (Note that z can be greater than n but not longer.) + * + * The output z is used as input to a multiplication: + * + * > Calculate u_1 = z s^(-1) mod n ... + * + * That means we don't need to reduce z mod n: The fact that it has bitlength <= n makes it + * almost reduced which is enough for the multiplication to be correct. + * (using a weaker notion of "almost reduced" than what we usually prove, but sufficient for all uses of it: `z < 2^ceil(log(n))`) + * + * In summary, this method just: + * - takes a 32 bytes hash + * - converts them to 3 limbs which collectively have L_n <= 256 bits + */ +function keccakOutputToScalar(hash: Bytes, Curve: typeof ForeignCurve) { + const L_n = Curve.Scalar.sizeInBits; + // keep it simple for now, avoid dealing with dropping bits + // TODO: what does "leftmost bits" mean? big-endian or little-endian? + // @noble/curves uses a right shift, dropping the least significant bits: + // https://github.com/paulmillr/noble-curves/blob/4007ee975bcc6410c2e7b504febc1d5d625ed1a4/src/abstract/weierstrass.ts#L933 + assert(L_n === 256, `Scalar sizes ${L_n} !== 256 not supported`); + assert(hash.length === 32, `hash length ${hash.length} !== 32 not supported`); + + // piece together into limbs + // bytes are big-endian, so the first byte is the most significant + assert(l === 88n); + let x2 = bytesToLimbBE(hash.bytes.slice(0, 10)); + let x1 = bytesToLimbBE(hash.bytes.slice(10, 21)); + let x0 = bytesToLimbBE(hash.bytes.slice(21, 32)); + + return new Curve.Scalar.AlmostReduced([x0, x1, x2]); +} + +function bytesToLimbBE(bytes_: UInt8[]) { + let bytes = bytes_.map((x) => x.value); + let n = bytes.length; + let limb = bytes[0]; + for (let i = 1; i < n; i++) { + limb = limb.mul(1n << 8n).add(bytes[i]); + } + return limb.seal(); +} diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts new file mode 100644 index 0000000000..788b95129e --- /dev/null +++ b/src/lib/foreign-field.ts @@ -0,0 +1,739 @@ +import { + mod, + Fp, + FiniteField, + createField, +} from '../bindings/crypto/finite_field.js'; +import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; +import { Provable } from './provable.js'; +import { Bool } from './bool.js'; +import { Tuple, TupleMap, TupleN } from './util/types.js'; +import { Field3 } from './gadgets/foreign-field.js'; +import { Gadgets } from './gadgets/gadgets.js'; +import { ForeignField as FF } from './gadgets/foreign-field.js'; +import { assert } from './gadgets/common.js'; +import { l3, l } from './gadgets/range-check.js'; +import { ProvablePureExtended } from './circuit_value.js'; + +// external API +export { createForeignField }; +export type { + ForeignField, + UnreducedForeignField, + AlmostForeignField, + CanonicalForeignField, +}; + +class ForeignField { + static _Bigint: FiniteField | undefined = undefined; + static _modulus: bigint | undefined = undefined; + + // static parameters + static get Bigint() { + assert(this._Bigint !== undefined, 'ForeignField class not initialized.'); + return this._Bigint; + } + static get modulus() { + assert(this._modulus !== undefined, 'ForeignField class not initialized.'); + return this._modulus; + } + get modulus() { + return (this.constructor as typeof ForeignField).modulus; + } + static get sizeInBits() { + return this.modulus.toString(2).length; + } + + /** + * The internal representation of a foreign field element, as a tuple of 3 limbs. + */ + value: Field3; + + get Constructor() { + return this.constructor as typeof ForeignField; + } + + /** + * Sibling classes that represent different ranges of field elements. + */ + static _variants: + | { + unreduced: typeof UnreducedForeignField; + almostReduced: typeof AlmostForeignField; + canonical: typeof CanonicalForeignField; + } + | undefined = undefined; + + /** + * Constructor for unreduced field elements. + */ + static get Unreduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.unreduced; + } + /** + * Constructor for field elements that are "almost reduced", i.e. lie in the range [0, 2^ceil(log2(p))). + */ + static get AlmostReduced() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.almostReduced; + } + /** + * Constructor for field elements that are fully reduced, i.e. lie in the range [0, p). + */ + static get Canonical() { + assert(this._variants !== undefined, 'ForeignField class not initialized.'); + return this._variants.canonical; + } + + /** + * Create a new {@link ForeignField} from a bigint, number, string or another ForeignField. + * @example + * ```ts + * let x = new ForeignField(5); + * ``` + */ + constructor(x: ForeignField | Field3 | bigint | number | string) { + const p = this.modulus; + if (x instanceof ForeignField) { + this.value = x.value; + return; + } + // Field3 + if (Array.isArray(x)) { + this.value = x; + return; + } + // constant + this.value = Field3.from(mod(BigInt(x), p)); + } + + /** + * Coerce the input to a {@link ForeignField}. + */ + static from(x: bigint | number | string): CanonicalForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField; + static from(x: ForeignField | bigint | number | string): ForeignField { + if (x instanceof this) return x; + return new this.Canonical(x); + } + + /** + * Checks whether this field element is a constant. + * + * See {@link FieldVar} to understand constants vs variables. + */ + isConstant() { + return Field3.isConstant(this.value); + } + + /** + * Convert this field element to a constant. + * + * See {@link FieldVar} to understand constants vs variables. + * + * **Warning**: This function is only useful in {@link Provable.witness} or {@link Provable.asProver} blocks, + * that is, in situations where the prover computes a value outside provable code. + */ + toConstant(): ForeignField { + let constantLimbs = Tuple.map(this.value, (l) => l.toConstant()); + return new this.Constructor(constantLimbs); + } + + /** + * Convert this field element to a bigint. + */ + toBigInt() { + return Field3.toBigint(this.value); + } + + /** + * Assert that this field element lies in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * Returns the field element as a {@link AlmostForeignField}. + * + * For a more efficient version of this for multiple field elements, see {@link assertAlmostReduced}. + * + * Note: this does not ensure that the field elements is in the canonical range [0, p). + * To assert that stronger property, there is {@link assertCanonical}. + * You should typically use {@link assertAlmostReduced} though, because it is cheaper to prove and sufficient for + * ensuring validity of all our non-native field arithmetic methods. + */ + assertAlmostReduced() { + // TODO: this is not very efficient, but the only way to abstract away the complicated + // range check assumptions and also not introduce a global context of pending range checks. + // we plan to get rid of bounds checks anyway, then this is just a multi-range check + let [x] = this.Constructor.assertAlmostReduced(this); + return x; + } + + /** + * Assert that one or more field elements lie in the range [0, 2^k), + * where k = ceil(log2(p)) and p is the foreign field modulus. + * + * This is most efficient than when checking a multiple of 3 field elements at once. + */ + static assertAlmostReduced>( + ...xs: T + ): TupleMap { + Gadgets.ForeignField.assertAlmostReduced( + xs.map((x) => x.value), + this.modulus, + { skipMrc: true } + ); + return Tuple.map(xs, this.AlmostReduced.unsafeFrom); + } + + /** + * Assert that this field element is fully reduced, + * i.e. lies in the range [0, p), where p is the foreign field modulus. + * + * Returns the field element as a {@link CanonicalForeignField}. + */ + assertCanonical() { + this.assertLessThan(this.modulus); + return this.Constructor.Canonical.unsafeFrom(this); + } + + // arithmetic with full constraints, for safe use + + /** + * Finite field addition + * @example + * ```ts + * x.add(2); // x + 2 mod p + * ``` + */ + add(y: ForeignField | bigint | number) { + return this.Constructor.sum([this, y], [1]); + } + + /** + * Finite field negation + * @example + * ```ts + * x.neg(); // -x mod p = p - x + * ``` + */ + neg() { + // this gets a special implementation because negation proves that the return value is almost reduced. + // it shows that r = f - x >= 0 or r = 0 (for x=0) over the integers, which implies r < f + // see also `Gadgets.ForeignField.assertLessThan()` + let xNeg = Gadgets.ForeignField.neg(this.value, this.modulus); + return new this.Constructor.AlmostReduced(xNeg); + } + + /** + * Finite field subtraction + * @example + * ```ts + * x.sub(1); // x - 1 mod p + * ``` + */ + sub(y: ForeignField | bigint | number) { + return this.Constructor.sum([this, y], [-1]); + } + + /** + * Sum (or difference) of multiple finite field elements. + * + * @example + * ```ts + * let z = ForeignField.sum([3, 2, 1], [-1, 1]); // 3 - 2 + 1 + * z.assertEquals(2); + * ``` + * + * This method expects a list of ForeignField-like values, `x0,...,xn`, + * and a list of "operations" `op1,...,opn` where every op is 1 or -1 (plus or minus), + * and returns + * + * `x0 + op1*x1 + ... + opn*xn` + * + * where the sum is computed in finite field arithmetic. + * + * **Important:** For more than two summands, this is significantly more efficient + * than chaining calls to {@link ForeignField.add} and {@link ForeignField.sub}. + * + */ + static sum(xs: (ForeignField | bigint | number)[], operations: (1 | -1)[]) { + const p = this.modulus; + let fields = xs.map((x) => toLimbs(x, p)); + let ops = operations.map((op) => (op === 1 ? 1n : -1n)); + let z = Gadgets.ForeignField.sum(fields, ops, p); + return new this.Unreduced(z); + } + + // convenience methods + + /** + * Assert equality with a ForeignField-like value + * + * @example + * ```ts + * x.assertEquals(0, "x is zero"); + * ``` + * + * Since asserting equality can also serve as a range check, + * this method returns `x` with the appropriate type: + * + * @example + * ```ts + * let xChecked = x.assertEquals(1, "x is 1"); + * xChecked satisfies CanonicalForeignField; + * ``` + */ + assertEquals( + y: bigint | number | CanonicalForeignField, + message?: string + ): CanonicalForeignField; + assertEquals(y: AlmostForeignField, message?: string): AlmostForeignField; + assertEquals(y: ForeignField, message?: string): ForeignField; + assertEquals( + y: ForeignField | bigint | number, + message?: string + ): ForeignField { + const p = this.modulus; + try { + if (this.isConstant() && isConstant(y)) { + let x = this.toBigInt(); + let y0 = mod(toBigInt(y), p); + if (x !== y0) { + throw Error(`ForeignField.assertEquals(): ${x} != ${y0}`); + } + return new this.Constructor.Canonical(this.value); + } + Provable.assertEqual( + this.Constructor.provable, + this, + new this.Constructor(y) + ); + if (isConstant(y) || y instanceof this.Constructor.Canonical) { + return new this.Constructor.Canonical(this.value); + } else if (y instanceof this.Constructor.AlmostReduced) { + return new this.Constructor.AlmostReduced(this.value); + } else { + return this; + } + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this field element is less than a constant c: `x < c`. + * + * The constant must satisfy `0 <= c < 2^264`, otherwise an error is thrown. + * + * @example + * ```ts + * x.assertLessThan(10); + * ``` + */ + assertLessThan(c: bigint | number, message?: string) { + assert( + c >= 0 && c < 1n << l3, + `ForeignField.assertLessThan(): expected c <= c < 2^264, got ${c}` + ); + try { + Gadgets.ForeignField.assertLessThan(this.value, toBigInt(c)); + } catch (err) { + throw withMessage(err, message); + } + } + + // bit packing + + /** + * Unpack a field element to its bits, as a {@link Bool}[] array. + * + * This method is provable! + */ + toBits(length?: number) { + const sizeInBits = this.Constructor.sizeInBits; + if (length === undefined) length = sizeInBits; + checkBitLength('ForeignField.toBits()', length, sizeInBits); + let [l0, l1, l2] = this.value; + let limbSize = Number(l); + let xBits = l0.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return xBits; + let yBits = l1.toBits(Math.min(length, limbSize)); + length -= limbSize; + if (length <= 0) return [...xBits, ...yBits]; + let zBits = l2.toBits(Math.min(length, limbSize)); + return [...xBits, ...yBits, ...zBits]; + } + + /** + * Create a field element from its bits, as a `Bool[]` array. + * + * This method is provable! + */ + static fromBits(bits: Bool[]) { + let length = bits.length; + checkBitLength('ForeignField.fromBits()', length, this.sizeInBits); + 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)); + // note: due to the check on the number of bits, we know we return an "almost valid" field element + return new this.AlmostReduced([l0, l1, l2]); + } + + static random() { + return new this.Canonical(this.Bigint.random()); + } + + /** + * Instance version of `Provable.toFields`, see {@link Provable.toFields} + */ + toFields(): Field[] { + return this.value; + } + + static check(_: ForeignField) { + throw Error('ForeignField.check() not implemented: must use a subclass'); + } + + static _provable: any = undefined; + + /** + * `Provable`, see {@link Provable} + */ + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } +} + +class ForeignFieldWithMul extends ForeignField { + /** + * Finite field multiplication + * @example + * ```ts + * x.mul(y); // x*y mod p + * ``` + */ + mul(y: AlmostForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.mul(this.value, toLimbs(y, p), p); + return new this.Constructor.Unreduced(z); + } + + /** + * Multiplicative inverse in the finite field + * @example + * ```ts + * let z = x.inv(); // 1/x mod p + * z.mul(x).assertEquals(1); + * ``` + */ + inv() { + const p = this.modulus; + let z = Gadgets.ForeignField.inv(this.value, p); + return new this.Constructor.AlmostReduced(z); + } + + /** + * Division in the finite field, i.e. `x*y^(-1) mod p` where `y^(-1)` is the finite field inverse. + * @example + * ```ts + * let z = x.div(y); // x/y mod p + * z.mul(y).assertEquals(x); + * ``` + */ + div(y: AlmostForeignField | bigint | number) { + const p = this.modulus; + let z = Gadgets.ForeignField.div(this.value, toLimbs(y, p), p); + return new this.Constructor.AlmostReduced(z); + } +} + +class UnreducedForeignField extends ForeignField { + type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + } +} + +class AlmostForeignField extends ForeignFieldWithMul { + type: 'AlmostReduced' | 'FullyReduced' = 'AlmostReduced'; + + constructor(x: AlmostForeignField | Field3 | bigint | number | string) { + super(x); + } + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + x.assertAlmostReduced(); + } + + /** + * Coerce the input to an {@link AlmostForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } + + /** + * Check equality with a constant value. + * + * @example + * ```ts + * let isXZero = x.equals(0); + * ``` + */ + equals(y: bigint | number) { + return FF.equals(this.value, BigInt(y), this.modulus); + } +} + +class CanonicalForeignField extends ForeignFieldWithMul { + type = 'FullyReduced' as const; + + constructor(x: CanonicalForeignField | Field3 | bigint | number | string) { + super(x); + } + + static _provable: + | ProvablePureExtended + | undefined = undefined; + static get provable() { + assert(this._provable !== undefined, 'ForeignField class not initialized.'); + return this._provable; + } + + static check(x: ForeignField) { + Gadgets.multiRangeCheck(x.value); + x.assertCanonical(); + } + + /** + * Coerce the input to a {@link CanonicalForeignField} without additional assertions. + * + * **Warning:** Only use if you know what you're doing. + */ + static unsafeFrom(x: ForeignField) { + return new this(x.value); + } + + /** + * Check equality with a ForeignField-like value. + * + * @example + * ```ts + * let isEqual = x.equals(y); + * ``` + * + * Note: This method only exists on canonical fields; on unreduced fields, it would be easy to + * misuse, because not being exactly equal does not imply being unequal modulo p. + */ + equals(y: CanonicalForeignField | bigint | number) { + let [x0, x1, x2] = this.value; + let [y0, y1, y2] = toLimbs(y, this.modulus); + let x01 = x0.add(x1.mul(1n << l)).seal(); + let y01 = y0.add(y1.mul(1n << l)).seal(); + return x01.equals(y01).and(x2.equals(y2)); + } +} + +function toLimbs( + x: bigint | number | string | ForeignField, + p: bigint +): Field3 { + if (x instanceof ForeignField) return x.value; + return Field3.from(mod(BigInt(x), p)); +} + +function toBigInt(x: bigint | string | number | ForeignField) { + if (x instanceof ForeignField) return x.toBigInt(); + return BigInt(x); +} + +function isConstant(x: bigint | number | string | ForeignField) { + if (x instanceof ForeignField) return x.isConstant(); + return true; +} + +/** + * Create a class representing a prime order finite field, which is different from the native {@link Field}. + * + * ```ts + * const SmallField = createForeignField(17n); // the finite field F_17 + * ``` + * + * `createForeignField(p)` takes the prime modulus `p` of the finite field as input, as a bigint. + * We support prime moduli up to a size of 259 bits. + * + * The returned {@link ForeignField} class supports arithmetic modulo `p` (addition and multiplication), + * as well as helper methods like `assertEquals()` and `equals()`. + * + * _Advanced details:_ + * + * Internally, a foreign field element is represented as three native field elements, each of which + * represents a limb of 88 bits. Therefore, being a valid foreign field element means that all 3 limbs + * fit in 88 bits, and the foreign field element altogether is smaller than the modulus p. + * + * Since the full `x < p` check is expensive, by default we only prove a weaker assertion, `x < 2^ceil(log2(p))`, + * see {@link ForeignField.assertAlmostReduced} for more details. + * + * This weaker assumption is what we call "almost reduced", and it is represented by the {@link AlmostForeignField} class. + * Note that only {@link AlmostForeignField} supports multiplication and inversion, while {@link UnreducedForeignField} + * only supports addition and subtraction. + * + * This function returns the `Unreduced` class, which will cause the minimum amount of range checks to be created by default. + * If you want to do multiplication, you have two options: + * - create your field elements using the {@link ForeignField.AlmostReduced} constructor, or using the `.provable` type on that class. + * ```ts + * let x = Provable.witness(ForeignField.AlmostReduced.provable, () => ForeignField.from(5)); + * ``` + * - create your field elements normally and convert them using `x.assertAlmostReduced()`. + * ```ts + * let xChecked = x.assertAlmostReduced(); // asserts x < 2^ceil(log2(p)); returns `AlmostForeignField` + * ``` + * + * Similarly, there is a separate class {@link CanonicalForeignField} which represents fully reduced, "canonical" field elements. + * To convert to a canonical field element, use {@link ForeignField.assertCanonical}: + * + * ```ts + * x.assertCanonical(); // asserts x < p; returns `CanonicalForeignField` + * ``` + * You will likely not need canonical fields most of the time. + * + * Base types for all of these classes are separately exported as {@link UnreducedForeignField}, {@link AlmostForeignField} and {@link CanonicalForeignField}., + * + * @param modulus the modulus of the finite field you are instantiating + */ +function createForeignField(modulus: bigint): typeof UnreducedForeignField { + assert( + modulus > 0n, + `ForeignField: modulus must be positive, got ${modulus}` + ); + assert( + modulus < foreignFieldMax, + `ForeignField: modulus exceeds the max supported size of 2^${foreignFieldMaxBits}` + ); + + let Bigint = createField(modulus); + + class UnreducedField extends UnreducedForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(UnreducedField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(UnreducedField); + static sum = ForeignField.sum.bind(UnreducedField); + static fromBits = ForeignField.fromBits.bind(UnreducedField); + } + + class AlmostField extends AlmostForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(AlmostField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(AlmostField); + static sum = ForeignField.sum.bind(AlmostField); + static fromBits = ForeignField.fromBits.bind(AlmostField); + static unsafeFrom = AlmostForeignField.unsafeFrom.bind(AlmostField); + } + + class CanonicalField extends CanonicalForeignField { + static _Bigint = Bigint; + static _modulus = modulus; + static _provable = provable(CanonicalField); + + // bind public static methods to the class so that they have `this` defined + static from = ForeignField.from.bind(CanonicalField); + static sum = ForeignField.sum.bind(CanonicalField); + static fromBits = ForeignField.fromBits.bind(CanonicalField); + static unsafeFrom = CanonicalForeignField.unsafeFrom.bind(CanonicalField); + } + + let variants = { + unreduced: UnreducedField, + almostReduced: AlmostField, + canonical: CanonicalField, + }; + UnreducedField._variants = variants; + AlmostField._variants = variants; + CanonicalField._variants = variants; + + return UnreducedField; +} + +// the max foreign field modulus is f_max = floor(sqrt(p * 2^t)), where t = 3*limbBits = 264 and p is the native modulus +// see RFC: https://github.com/o1-labs/proof-systems/blob/1fdb1fd1d112f9d4ee095dbb31f008deeb8150b0/book/src/rfcs/foreign_field_mul.md +// since p = 2^254 + eps for both Pasta fields with eps small, a fairly tight lower bound is +// f_max >= sqrt(2^254 * 2^264) = 2^259 +const foreignFieldMaxBits = (BigInt(Fp.sizeInBits - 1) + 3n * l) / 2n; +const foreignFieldMax = 1n << foreignFieldMaxBits; + +// provable + +type Constructor = new (...args: any[]) => T; + +function provable( + Class: Constructor & { check(x: ForeignField): void } +): ProvablePureExtended { + return { + toFields(x) { + return x.value; + }, + toAuxiliary(): [] { + return []; + }, + sizeInFields() { + return 3; + }, + fromFields(fields) { + let limbs = TupleN.fromArray(3, fields); + return new Class(limbs); + }, + check(x: ForeignField) { + Class.check(x); + }, + // ugh + toJSON(x: ForeignField) { + return x.toBigInt().toString(); + }, + fromJSON(x: string) { + // TODO be more strict about allowed values + return new Class(x); + }, + empty() { + return new Class(0n); + }, + toInput(x) { + let l_ = Number(l); + return { + packed: [ + [x.value[0], l_], + [x.value[1], l_], + [x.value[2], l_], + ], + }; + }, + }; +} diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts new file mode 100644 index 0000000000..26f85a5699 --- /dev/null +++ b/src/lib/foreign-field.unit-test.ts @@ -0,0 +1,191 @@ +import { ProvablePure } from '../snarky.js'; +import { Field, Group } from './core.js'; +import { ForeignField, createForeignField } from './foreign-field.js'; +import { Scalar as Fq, Group as G } from '../provable/curve-bigint.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 './circuit.js'; +import { Scalar } from './scalar.js'; +import { l } from './gadgets/range-check.js'; +import { assert } from './gadgets/common.js'; + +// toy example - F_17 + +class SmallField extends createForeignField(17n) {} + +let x = SmallField.from(16); +x.assertEquals(-1); // 16 = -1 (mod 17) +x.mul(x).assertEquals(1); // 16 * 16 = 15 * 17 + 1 = 1 (mod 17) + +// invalid example - modulus too large + +expect(() => createForeignField(1n << 260n)).toThrow( + 'modulus exceeds the max supported size' +); + +// real example - foreign field arithmetic in the Pallas scalar field + +class ForeignScalar extends createForeignField(Fq.modulus) {} + +// types +ForeignScalar.provable satisfies ProvablePure; + +// basic constructor / IO +{ + let s0 = 1n + ((1n + (1n << l)) << l); + let scalar = new ForeignScalar(s0); + + expect(scalar.value).toEqual([Field(1), Field(1), Field(1)]); + expect(scalar.toBigInt()).toEqual(s0); +} + +test(Random.scalar, (x0, assert) => { + let x = new ForeignScalar(x0); + assert(x.toBigInt() === x0); + assert(x.isConstant()); +}); + +// test equivalence of in-SNARK and out-of-SNARK operations + +let f = spec({ + rng: Random.scalar, + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.AlmostReduced.provable, +}); +let u264 = spec({ + rng: Random.bignat(1n << 264n), + there: ForeignScalar.from, + back: (x) => x.toBigInt(), + provable: ForeignScalar.Unreduced.provable, +}); + +// arithmetic +equivalent({ from: [f, f], to: u264 })(Fq.add, (x, y) => x.add(y)); +equivalent({ from: [f, f], to: u264 })(Fq.sub, (x, y) => x.sub(y)); +equivalent({ from: [f], to: u264 })(Fq.negate, (x) => x.neg()); +equivalent({ from: [f, f], to: u264 })(Fq.mul, (x, y) => x.mul(y)); +equivalent({ from: [f], to: f })( + (x) => Fq.inverse(x) ?? throwError('division by 0'), + (x) => x.inv() +); +equivalent({ from: [f, f], to: f })( + (x, y) => Fq.div(x, y) ?? throwError('division by 0'), + (x, y) => x.div(y) +); + +// equality with a constant +equivalent({ from: [f, first(f)], to: bool })( + (x, y) => x === y, + (x, y) => x.equals(y) +); +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 })( + (x, y) => x < y || throwError('not less than'), + (x, y) => x.assertLessThan(y) +); + +// toBits / fromBits +equivalent({ from: [f], to: f })( + (x) => x, + (x) => { + let bits = x.toBits(); + expect(bits.length).toEqual(255); + return ForeignScalar.fromBits(bits); + } +); + +// scalar shift in foreign field arithmetic vs in the exponent + +let scalarShift = Fq(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 = G.scale(G.generatorMina, 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(); +Provable.runAndCheck(main0); +Provable.runAndCheck(main1); + +// using foreign field arithmetic should result in much fewer constraints +let { rows: rows0 } = Provable.constraintSystem(main0); +let { rows: rows1 } = 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'); diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index fb3189f3ca..8f6f218935 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -32,7 +32,7 @@ function arrayGet(array: Field[], index: Field) { // witness result let a = existsOne(() => array[Number(i.toBigInt())].toBigInt()); - // we prove a === array[j] + zj*(i - j) for some zj, for all j. + // we prove a === array[j] + z[j]*(i - j) for some z[j], for all j. // setting j = i, this implies a === array[i] // thanks to our assumption that the index i is within bounds, we know that j = i for some j let n = array.length; @@ -44,7 +44,7 @@ function arrayGet(array: Field[], index: Field) { ); return zj ?? 0n; }); - // prove that zj*(i - j) === a - array[j] + // prove that z[j]*(i - j) === a - array[j] // TODO abstract this logic into a general-purpose assertMul() gadget, // which is able to use the constant coefficient // (snarky's assert_r1cs somehow leads to much more constraints than this) @@ -125,6 +125,10 @@ function assertBilinear( // b*x + c*y - z? + a*x*y + d === 0 Gates.generic( { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, - { left: x, right: y, out: z === undefined ? x : z } + { left: x, right: y, out: z === undefined ? emptyCell() : z } ); } + +function emptyCell() { + return existsOne(() => 0n); +} diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 4a8a66f041..f4bb9551db 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -5,10 +5,10 @@ import { Gates } from '../gates.js'; import { MAX_BITS, assert, - witnessSlice, - witnessNextValue, divideWithRemainder, toVar, + exists, + bitSlice, } from './common.js'; import { rangeCheck64 } from './range-check.js'; @@ -20,8 +20,8 @@ function not(a: Field, length: number, checked: boolean = false) { // Check that length does not exceed maximum field size in bits assert( - length < Field.sizeInBits(), - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + length < Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` ); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table @@ -61,15 +61,8 @@ function xor(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert( - a.toBigInt() < max, - `${a.toBigInt()} does not fit into ${padLength} bits` - ); - - assert( - b.toBigInt() < max, - `${b.toBigInt()} does not fit into ${padLength} bits` - ); + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); return new Field(a.toBigInt() ^ b.toBigInt()); } @@ -88,66 +81,71 @@ function xor(a: Field, b: Field, length: number) { } // builds a xor chain -function buildXor( - a: Field, - b: Field, - expectedOutput: Field, - padLength: number -) { +function buildXor(a: Field, b: Field, out: Field, padLength: number) { // construct the chain of XORs until padLength is 0 while (padLength !== 0) { // slices the inputs into 4x 4bit-sized chunks - // slices of a - let in1_0 = witnessSlice(a, 0, 4); - let in1_1 = witnessSlice(a, 4, 4); - let in1_2 = witnessSlice(a, 8, 4); - let in1_3 = witnessSlice(a, 12, 4); - - // slices of b - let in2_0 = witnessSlice(b, 0, 4); - let in2_1 = witnessSlice(b, 4, 4); - let in2_2 = witnessSlice(b, 8, 4); - let in2_3 = witnessSlice(b, 12, 4); - - // slices of expected output - let out0 = witnessSlice(expectedOutput, 0, 4); - let out1 = witnessSlice(expectedOutput, 4, 4); - let out2 = witnessSlice(expectedOutput, 8, 4); - let out3 = witnessSlice(expectedOutput, 12, 4); + let slices = exists(15, () => { + let a0 = a.toBigInt(); + let b0 = b.toBigInt(); + let out0 = out.toBigInt(); + return [ + // slices of a + bitSlice(a0, 0, 4), + bitSlice(a0, 4, 4), + bitSlice(a0, 8, 4), + bitSlice(a0, 12, 4), + + // slices of b + bitSlice(b0, 0, 4), + bitSlice(b0, 4, 4), + bitSlice(b0, 8, 4), + bitSlice(b0, 12, 4), + + // slices of expected output + bitSlice(out0, 0, 4), + bitSlice(out0, 4, 4), + bitSlice(out0, 8, 4), + bitSlice(out0, 12, 4), + + // next values + a0 >> 16n, + b0 >> 16n, + out0 >> 16n, + ]; + }); + + // prettier-ignore + let [ + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3, + aNext, bNext, outNext + ] = slices; // assert that the xor of the slices is correct, 16 bit at a time + // prettier-ignore Gates.xor( - a, - b, - expectedOutput, - in1_0, - in1_1, - in1_2, - in1_3, - in2_0, - in2_1, - in2_2, - in2_3, - out0, - out1, - out2, - out3 + a, b, out, + in1_0, in1_1, in1_2, in1_3, + in2_0, in2_1, in2_2, in2_3, + out0, out1, out2, out3 ); // update the values for the next loop iteration - a = witnessNextValue(a); - b = witnessNextValue(b); - expectedOutput = witnessNextValue(expectedOutput); + a = aNext; + b = bNext; + out = outNext; padLength = padLength - 16; } // inputs are zero and length is zero, add the zero check - we reached the end of our chain - Gates.zero(a, b, expectedOutput); + Gates.zero(a, b, out); let zero = new Field(0); zero.assertEquals(a); zero.assertEquals(b); - zero.assertEquals(expectedOutput); + zero.assertEquals(out); } function and(a: Field, b: Field, length: number) { @@ -156,8 +154,8 @@ function and(a: Field, b: Field, length: number) { // check that length does not exceed maximum field size in bits assert( - length <= Field.sizeInBits(), - `Length ${length} exceeds maximum of ${Field.sizeInBits()} bits.` + length <= Field.sizeInBits, + `Length ${length} exceeds maximum of ${Field.sizeInBits} bits.` ); // obtain pad length until the length is a multiple of 16 for n-bit length lookup table @@ -167,15 +165,8 @@ function and(a: Field, b: Field, length: number) { if (a.isConstant() && b.isConstant()) { let max = 1n << BigInt(padLength); - assert( - a.toBigInt() < max, - `${a.toBigInt()} does not fit into ${padLength} bits` - ); - - assert( - b.toBigInt() < max, - `${b.toBigInt()} does not fit into ${padLength} bits` - ); + assert(a.toBigInt() < max, `${a} does not fit into ${padLength} bits`); + assert(b.toBigInt() < max, `${b} does not fit into ${padLength} bits`); return new Field(a.toBigInt() & b.toBigInt()); } @@ -251,27 +242,34 @@ function rot( // TODO this is an abstraction leak, but not clear to me how to improve toVar(0n); + // slice the bound into chunks + let boundSlices = exists(12, () => { + let bound0 = bound.toBigInt(); + return [ + bitSlice(bound0, 52, 12), // bits 52-64 + bitSlice(bound0, 40, 12), // bits 40-52 + bitSlice(bound0, 28, 12), // bits 28-40 + bitSlice(bound0, 16, 12), // bits 16-28 + + bitSlice(bound0, 14, 2), // bits 14-16 + bitSlice(bound0, 12, 2), // bits 12-14 + bitSlice(bound0, 10, 2), // bits 10-12 + bitSlice(bound0, 8, 2), // bits 8-10 + bitSlice(bound0, 6, 2), // bits 6-8 + bitSlice(bound0, 4, 2), // bits 4-6 + bitSlice(bound0, 2, 2), // bits 2-4 + bitSlice(bound0, 0, 2), // bits 0-2 + ]; + }); + let [b52, b40, b28, b16, b14, b12, b10, b8, b6, b4, b2, b0] = boundSlices; + // Compute current row Gates.rotate( field, rotated, excess, - [ - witnessSlice(bound, 52, 12), // bits 52-64 - witnessSlice(bound, 40, 12), // bits 40-52 - witnessSlice(bound, 28, 12), // bits 28-40 - witnessSlice(bound, 16, 12), // bits 16-28 - ], - [ - witnessSlice(bound, 14, 2), // bits 14-16 - witnessSlice(bound, 12, 2), // bits 12-14 - witnessSlice(bound, 10, 2), // bits 10-12 - witnessSlice(bound, 8, 2), // bits 8-10 - witnessSlice(bound, 6, 2), // bits 6-8 - witnessSlice(bound, 4, 2), // bits 4-6 - witnessSlice(bound, 2, 2), // bits 2-4 - witnessSlice(bound, 0, 2), // bits 0-2 - ], + [b52, b40, b28, b16], + [b14, b12, b10, b8, b6, b4, b2, b0], big2PowerRot ); // Compute next row diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index e6e6c873cc..9da4490723 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,4 +1,3 @@ -import { Provable } from '../provable.js'; import { Field, FieldConst, FieldVar, VarField } from '../field.js'; import { Tuple, TupleN } from '../util/types.js'; import { Snarky } from '../../snarky.js'; @@ -15,8 +14,6 @@ export { isVar, assert, bitSlice, - witnessSlice, - witnessNextValue, divideWithRemainder, }; @@ -75,19 +72,6 @@ function bitSlice(x: bigint, start: number, length: number) { return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n); } -function witnessSlice(f: Field, start: number, length: number) { - if (length <= 0) throw Error('Length must be a positive number'); - - return Provable.witness(Field, () => { - let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); - }); -} - -function witnessNextValue(current: Field) { - return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); -} - function divideWithRemainder(numerator: bigint, denominator: bigint) { const quotient = numerator / denominator; const remainder = numerator - denominator * quotient; diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index da11c384f5..b03266b1a2 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -3,6 +3,7 @@ import { Ecdsa, EllipticCurve, Point, + initialAggregator, verifyEcdsaConstant, } from './elliptic-curve.js'; import { Field3 } from './foreign-field.js'; @@ -10,16 +11,18 @@ import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; import { Provable } from '../provable.js'; import { ZkProgram } from '../proof_system.js'; import { assert } from './common.js'; -import { foreignField, throwError, uniformForeignField } from './test-utils.js'; +import { foreignField, uniformForeignField } from './test-utils.js'; import { + First, Second, + bool, equivalentProvable, fromRandom, map, oneOf, record, - unit, } from '../testing/equivalent.js'; +import { Bool } from '../bool.js'; import { Random } from '../testing/random.js'; // quick tests @@ -34,7 +37,8 @@ for (let Curve of curves) { let scalar = foreignField(Curve.Scalar); let privateKey = uniformForeignField(Curve.Scalar); - let pseudoSignature = record({ + // correct signature shape, but independently random components, which will never form a valid signature + let badSignature = record({ signature: record({ r: scalar, s: scalar }), msg: scalar, publicKey: record({ x: field, y: field }), @@ -43,7 +47,7 @@ for (let Curve of curves) { let signatureInputs = record({ privateKey, msg: scalar }); let signature = map( - { from: signatureInputs, to: pseudoSignature }, + { from: signatureInputs, to: badSignature }, ({ privateKey, msg }) => { let publicKey = Curve.scale(Curve.one, privateKey); let signature = Ecdsa.sign(Curve, msg, privateKey); @@ -56,39 +60,55 @@ for (let Curve of curves) { // provable method we want to test const verify = (s: Second, noGlv: boolean) => { + // invalid public key can lead to either a failing constraint, or verify() returning false + EllipticCurve.assertOnCurve(s.publicKey, Curve); + let hasGlv = Curve.hasEndomorphism; if (noGlv) Curve.hasEndomorphism = false; // hack to force non-GLV version try { - Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); + return Ecdsa.verify(Curve, s.signature, s.msg, s.publicKey); } finally { Curve.hasEndomorphism = hasGlv; } }; + // input validation equivalent to the one implicit in verify() + const checkInputs = ({ + signature: { r, s }, + publicKey, + }: First) => { + assert(r !== 0n && s !== 0n, 'invalid signature'); + let pk = Curve.fromNonzero(publicKey); + assert(Curve.isOnCurve(pk), 'invalid public key'); + return true; + }; + // positive test - equivalentProvable({ from: [signature, noGlv], to: unit })( - () => {}, + equivalentProvable({ from: [signature, noGlv], to: bool, verbose: true })( + () => true, verify, - 'valid signature verifies' + `${Curve.name}: verifies` ); // negative test - equivalentProvable({ from: [pseudoSignature, noGlv], to: unit })( - () => throwError('invalid signature'), + equivalentProvable({ from: [badSignature, noGlv], to: bool, verbose: true })( + (s) => checkInputs(s) && false, verify, - 'invalid signature fails' + `${Curve.name}: fails` ); // test against constant implementation, with both invalid and valid signatures equivalentProvable({ - from: [oneOf(signature, pseudoSignature), noGlv], - to: unit, + from: [oneOf(signature, badSignature), noGlv], + to: bool, + verbose: true, })( ({ signature, publicKey, msg }) => { - assert(verifyEcdsaConstant(Curve, signature, msg, publicKey), 'verifies'); + checkInputs({ signature, publicKey, msg }); + return verifyEcdsaConstant(Curve, signature, msg, publicKey); }, verify, - 'verify' + `${Curve.name}: verify` ); } @@ -108,28 +128,13 @@ let msgHash = 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn ); -const ia = EllipticCurve.initialAggregator(Secp256k1); +const ia = initialAggregator(Secp256k1); const config = { G: { windowSize: 4 }, P: { windowSize: 3 }, ia }; let program = ZkProgram({ name: 'ecdsa', + publicOutput: Bool, methods: { - scale: { - privateInputs: [], - method() { - let G = Point.from(Secp256k1.one); - let P = Provable.witness(Point.provable, () => publicKey); - let R = EllipticCurve.multiScalarMul( - Secp256k1, - [signature.s, signature.r], - [G, P], - [config.G, config.P] - ); - Provable.asProver(() => { - console.log(Point.toBigint(R)); - }); - }, - }, ecdsa: { privateInputs: [], method() { @@ -140,33 +145,31 @@ let program = ZkProgram({ let msgHash_ = Provable.witness(Field3.provable, () => msgHash); let publicKey_ = Provable.witness(Point.provable, () => publicKey); - Ecdsa.verify(Secp256k1, signature_, msgHash_, publicKey_, config); + return Ecdsa.verify( + Secp256k1, + signature_, + msgHash_, + publicKey_, + config + ); }, }, }, }); -let main = program.rawMethods.ecdsa; console.time('ecdsa verify (constant)'); -main(); +program.rawMethods.ecdsa(); console.timeEnd('ecdsa verify (constant)'); console.time('ecdsa verify (witness gen / check)'); -Provable.runAndCheck(main); +Provable.runAndCheck(program.rawMethods.ecdsa); console.timeEnd('ecdsa verify (witness gen / check)'); console.time('ecdsa verify (build constraint system)'); -let cs = Provable.constraintSystem(main); +let cs = program.analyzeMethods().ecdsa; console.timeEnd('ecdsa verify (build constraint system)'); -let gateTypes: Record = {}; -gateTypes['Total rows'] = cs.rows; -for (let gate of cs.gates) { - gateTypes[gate.type] ??= 0; - gateTypes[gate.type]++; -} - -console.log(gateTypes); +console.log(cs.summary()); console.time('ecdsa verify (compile)'); await program.compile(); @@ -177,3 +180,4 @@ let proof = await program.ecdsa(); console.timeEnd('ecdsa verify (prove)'); assert(await program.verify(proof), 'proof verifies'); +proof.publicOutput.assertTrue('signature verifies'); diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index bd48edb137..083ab64f53 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -24,13 +24,16 @@ import { arrayGet, assertBoolean } from './basic.js'; export { EllipticCurve, Point, Ecdsa }; // internal API -export { verifyEcdsaConstant }; +export { verifyEcdsaConstant, initialAggregator, simpleMapToCurve }; const EllipticCurve = { add, double, + negate, + assertOnCurve, + scale, + assertInSubgroup, multiScalarMul, - initialAggregator, }; /** @@ -47,9 +50,10 @@ namespace Ecdsa { export type signature = { r: bigint; s: bigint }; } -function add(p1: Point, p2: Point, f: bigint) { +function add(p1: Point, p2: Point, Curve: { modulus: bigint }) { let { x: x1, y: y1 } = p1; let { x: x2, y: y2 } = p2; + let f = Curve.modulus; // constant case if (Point.isConstant(p1) && Point.isConstant(p2)) { @@ -73,7 +77,7 @@ function add(p1: Point, p2: Point, f: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - ForeignField.assertAlmostFieldElements([m, x3, y3], f); + ForeignField.assertAlmostReduced([m, x3, y3], f); // (x1 - x2)*m = y1 - y2 let deltaX = ForeignField.Sum(x1).sub(x2); @@ -92,8 +96,9 @@ function add(p1: Point, p2: Point, f: bigint) { return { x: x3, y: y3 }; } -function double(p1: Point, f: bigint) { +function double(p1: Point, Curve: { modulus: bigint; a: bigint }) { let { x: x1, y: y1 } = p1; + let f = Curve.modulus; // constant case if (Point.isConstant(p1)) { @@ -117,16 +122,17 @@ function double(p1: Point, f: bigint) { let m: Field3 = [m0, m1, m2]; let x3: Field3 = [x30, x31, x32]; let y3: Field3 = [y30, y31, y32]; - ForeignField.assertAlmostFieldElements([m, x3, y3], f); + ForeignField.assertAlmostReduced([m, x3, y3], f); // x1^2 = x1x1 let x1x1 = ForeignField.mul(x1, x1, f); - // 2*y1*m = 3*x1x1 - // TODO this assumes the curve has a == 0 + // 2*y1*m = 3*x1x1 + a let y1Times2 = ForeignField.Sum(y1).add(y1); - let x1x1Times3 = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); - ForeignField.assertMul(y1Times2, m, x1x1Times3, f); + let x1x1Times3PlusA = ForeignField.Sum(x1x1).add(x1x1).add(x1x1); + if (Curve.a !== 0n) + x1x1Times3PlusA = x1x1Times3PlusA.add(Field3.from(Curve.a)); + ForeignField.assertMul(y1Times2, m, x1x1Times3PlusA, f); // m^2 = 2*x1 + x3 let xSum = ForeignField.Sum(x1).add(x1).add(x3); @@ -140,6 +146,78 @@ function double(p1: Point, f: bigint) { return { x: x3, y: y3 }; } +function negate({ x, y }: Point, Curve: { modulus: bigint }) { + return { x, y: ForeignField.negate(y, Curve.modulus) }; +} + +function assertOnCurve( + p: Point, + { modulus: f, a, b }: { modulus: bigint; b: bigint; a: bigint } +) { + let { x, y } = p; + let x2 = ForeignField.mul(x, x, f); + let y2 = ForeignField.mul(y, y, f); + let y2MinusB = ForeignField.Sum(y2).sub(Field3.from(b)); + + // (x^2 + a) * x = y^2 - b + let x2PlusA = ForeignField.Sum(x2); + if (a !== 0n) x2PlusA = x2PlusA.add(Field3.from(a)); + let message: string | undefined; + if (Point.isConstant(p)) { + message = `assertOnCurve(): (${x}, ${y}) is not on the curve.`; + } + ForeignField.assertMul(x2PlusA, x, y2MinusB, f, message); +} + +/** + * EC scalar multiplication, `scalar*point` + * + * The result is constrained to be not zero. + */ +function scale( + scalar: Field3, + point: Point, + Curve: CurveAffine, + config: { + mode?: 'assert-nonzero' | 'assert-zero'; + windowSize?: number; + multiples?: Point[]; + } = { mode: 'assert-nonzero' } +) { + config.windowSize ??= Point.isConstant(point) ? 4 : 3; + return multiScalarMul([scalar], [point], Curve, [config], config.mode); +} + +// checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0 +function assertInSubgroup(p: Point, Curve: CurveAffine) { + if (!Curve.hasCofactor) return; + scale(Field3.from(Curve.order), p, Curve, { mode: 'assert-zero' }); +} + +// check whether a point equals a constant point +// TODO implement the full case of two vars +function equals(p1: Point, p2: point, Curve: { modulus: bigint }) { + let xEquals = ForeignField.equals(p1.x, p2.x, Curve.modulus); + let yEquals = ForeignField.equals(p1.y, p2.y, Curve.modulus); + return xEquals.and(yEquals); +} + +/** + * Verify an ECDSA signature. + * + * Details about the `config` parameter: + * - For both the generator point `G` and public key `P`, `config` allows you to specify: + * - the `windowSize` which is used in scalar multiplication for this point. + * this flexibility is good because the optimal window size is different for constant and non-constant points. + * empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. + * our defaults reflect that the generator is always constant and the public key is variable in typical applications. + * - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. + * if these are not provided, they are computed on the fly. + * for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. + * for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. + * in that case, passing them in would avoid computing them in-circuit and save a few constraints. + * - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. + */ function verifyEcdsa( Curve: CurveAffine, signature: Ecdsa.Signature, @@ -163,8 +241,7 @@ function verifyEcdsa( Field3.toBigint(msgHash), Point.toBigint(publicKey) ); - assert(isValid, 'invalid signature'); - return; + return new Bool(isValid); } // provable case @@ -179,19 +256,23 @@ function verifyEcdsa( let G = Point.from(Curve.one); let R = multiScalarMul( - Curve, [u1, u2], [G, publicKey], + Curve, config && [config.G, config.P], + 'assert-nonzero', config?.ia ); // this ^ already proves that R != 0 (part of ECDSA verification) // reduce R.x modulo the curve order - // note: we don't check that the result Rx is canonical, because Rx === r and r is an input: - // it's the callers responsibility to check that the signature is valid/unique in whatever way it makes sense for the application let Rx = ForeignField.mul(R.x, Field3.from(1n), Curve.order); - Provable.assertEqual(Field3.provable, Rx, r); + + // we have to prove that Rx is canonical, because we check signature validity based on whether Rx _exactly_ equals the input r. + // if we allowed non-canonical Rx, the prover could make verify() return false on a valid signature, by adding a multiple of `Curve.order` to Rx. + ForeignField.assertLessThan(Rx, Curve.order); + + return Provable.equal(Field3.provable, Rx, r); } /** @@ -203,8 +284,8 @@ function verifyEcdsaConstant( msgHash: bigint, publicKey: point ) { - let pk = Curve.fromNonzero(publicKey); - if (!Curve.isOnCurve(pk)) return false; + let pk = Curve.from(publicKey); + if (Curve.equal(pk, Curve.zero)) return false; if (Curve.hasCofactor && !Curve.isInSubgroup(pk)) return false; if (r < 1n || r >= Curve.order) return false; if (s < 1n || s >= Curve.order) return false; @@ -225,9 +306,14 @@ function verifyEcdsaConstant( * * s_0 * P_0 + ... + s_(n-1) * P_(n-1) * - * where P_i are any points. The result is not allowed to be zero. + * where P_i are any points. + * + * By default, we prove that the result is not zero. * - * We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. + * If you set the `mode` parameter to `'assert-zero'`, on the other hand, + * we assert that the result is zero and just return the constant zero point. + * + * Implementation: We double all points together and leverage a precomputed table of size 2^c to avoid all but every cth addition. * * Note: this algorithm targets a small number of points, like 2 needed for ECDSA verification. * @@ -235,13 +321,14 @@ function verifyEcdsaConstant( * TODO: custom bit representation for the scalar that avoids 0, to get rid of the degenerate addition case */ function multiScalarMul( - Curve: CurveAffine, scalars: Field3[], points: Point[], + Curve: CurveAffine, tableConfigs: ( | { windowSize?: number; multiples?: Point[] } | undefined )[] = [], + mode: 'assert-nonzero' | 'assert-zero' = 'assert-nonzero', ia?: point ): Point { let n = points.length; @@ -262,6 +349,11 @@ function multiScalarMul( sum = Curve.add(sum, Curve.scale(P[i], s[i])); } } + if (mode === 'assert-zero') { + assert(sum.infinity, 'scalar multiplication: expected zero result'); + return Point.from(Curve.zero); + } + assert(!sum.infinity, 'scalar multiplication: expected non-zero result'); return Point.from(sum); } @@ -338,7 +430,7 @@ function multiScalarMul( : arrayGetGeneric(Point.provable, tables[j], sj); // ec addition - let added = add(sum, sjP, Curve.modulus); + let added = add(sum, sjP, Curve); // handle degenerate case (if sj = 0, Gj is all zeros and the add result is garbage) sum = Provable.if(sj.equals(0), Point.provable, sum, added); @@ -349,14 +441,22 @@ function multiScalarMul( // jointly double all points // (note: the highest couple of bits will not create any constraints because sum is constant; no need to handle that explicitly) - sum = double(sum, Curve.modulus); + sum = double(sum, Curve); } // the sum is now 2^(b-1)*IA + sum_i s_i*P_i // we assert that sum != 2^(b-1)*IA, and add -2^(b-1)*IA to get our result let iaFinal = Curve.scale(Curve.fromNonzero(ia), 1n << BigInt(maxBits - 1)); - Provable.equal(Point.provable, sum, Point.from(iaFinal)).assertFalse(); - sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve.modulus); + let isZero = equals(sum, iaFinal, Curve); + + if (mode === 'assert-nonzero') { + isZero.assertFalse(); + sum = add(sum, Point.from(Curve.negate(iaFinal)), Curve); + } else { + isZero.assertTrue(); + // for type consistency with the 'assert-nonzero' case + sum = Point.from(Curve.zero); + } return sum; } @@ -460,10 +560,10 @@ function getPointTable( table = [Point.from(Curve.zero), P]; if (n === 2) return table; - let Pi = double(P, Curve.modulus); + let Pi = double(P, Curve); table.push(Pi); for (let i = 3; i < n; i++) { - Pi = add(Pi, P, Curve.modulus); + Pi = add(Pi, P, Curve); table.push(Pi); } return table; @@ -491,6 +591,22 @@ function initialAggregator(Curve: CurveAffine) { // use that as x coordinate const F = Curve.Field; let x = F.mod(bytesToBigInt(bytes)); + return simpleMapToCurve(x, Curve); +} + +function random(Curve: CurveAffine) { + let x = Curve.Field.random(); + return simpleMapToCurve(x, Curve); +} + +/** + * Given an x coordinate (base field element), increment it until we find one with + * a y coordinate that satisfies the curve equation, and return the point. + * + * If the curve has a cofactor, multiply by it to get a point in the correct subgroup. + */ +function simpleMapToCurve(x: bigint, Curve: CurveAffine) { + const F = Curve.Field; let y: bigint | undefined = undefined; // increment x until we find a y coordinate @@ -501,7 +617,13 @@ function initialAggregator(Curve: CurveAffine) { let y2 = F.add(x3, F.mul(Curve.a, x) + Curve.b); y = F.sqrt(y2); } - return { x, y, infinity: false }; + let p = { x, y, infinity: false }; + + // clear cofactor + if (Curve.hasCofactor) { + p = Curve.scale(p, Curve.cofactor!); + } + return p; } /** @@ -561,7 +683,7 @@ function sliceField( let chunks = []; let sum = Field.from(0n); - // if there's a leftover chunk from a previous slizeField() call, we complete it + // if there's a leftover chunk from a previous sliceField() call, we complete it if (leftover !== undefined) { let { chunks: previous, leftoverSize: size } = leftover; let remainingChunk = Field.from(0n); @@ -631,6 +753,13 @@ const Point = { }, isConstant: (P: Point) => Provable.isConstant(Point.provable, P), + /** + * Random point on the curve. + */ + random(Curve: CurveAffine) { + return Point.from(random(Curve)); + }, + provable: provable({ x: Field3.provable, y: Field3.provable }), }; diff --git a/src/lib/gadgets/elliptic-curve.unit-test.ts b/src/lib/gadgets/elliptic-curve.unit-test.ts new file mode 100644 index 0000000000..39a6d41ce0 --- /dev/null +++ b/src/lib/gadgets/elliptic-curve.unit-test.ts @@ -0,0 +1,82 @@ +import { CurveParams } from '../../bindings/crypto/elliptic-curve-examples.js'; +import { createCurveAffine } from '../../bindings/crypto/elliptic_curve.js'; +import { + array, + equivalentProvable, + map, + onlyIf, + spec, + unit, +} from '../testing/equivalent.js'; +import { Random } from '../testing/random.js'; +import { assert } from './common.js'; +import { EllipticCurve, Point, simpleMapToCurve } from './elliptic-curve.js'; +import { foreignField, throwError } from './test-utils.js'; + +// provable equivalence tests +const Secp256k1 = createCurveAffine(CurveParams.Secp256k1); +const Pallas = createCurveAffine(CurveParams.Pallas); +const Vesta = createCurveAffine(CurveParams.Vesta); +let curves = [Secp256k1, Pallas, Vesta]; + +for (let Curve of curves) { + // prepare test inputs + let field = foreignField(Curve.Field); + let scalar = foreignField(Curve.Scalar); + + // point shape, but with independently random components, which will never form a valid point + let badPoint = spec({ + rng: Random.record({ + x: field.rng, + y: field.rng, + infinity: Random.constant(false), + }), + there: Point.from, + back: Point.toBigint, + provable: Point.provable, + }); + + // valid random point + let point = map({ from: field, to: badPoint }, (x) => + simpleMapToCurve(x, Curve) + ); + + // two random points that are not equal, so are a valid input to EC addition + let unequalPair = onlyIf(array(point, 2), ([p, q]) => !Curve.equal(p, q)); + + // test ec gadgets witness generation + + equivalentProvable({ from: [unequalPair], to: point, verbose: true })( + ([p, q]) => Curve.add(p, q), + ([p, q]) => EllipticCurve.add(p, q, Curve), + `${Curve.name} add` + ); + + equivalentProvable({ from: [point], to: point, verbose: true })( + Curve.double, + (p) => EllipticCurve.double(p, Curve), + `${Curve.name} double` + ); + + equivalentProvable({ from: [point], to: point, verbose: true })( + Curve.negate, + (p) => EllipticCurve.negate(p, Curve), + `${Curve.name} negate` + ); + + equivalentProvable({ from: [point], to: unit, verbose: true })( + (p) => Curve.isOnCurve(p) || throwError('expect on curve'), + (p) => EllipticCurve.assertOnCurve(p, Curve), + `${Curve.name} on curve` + ); + + equivalentProvable({ from: [point, scalar], to: point, verbose: true })( + (p, s) => { + let sp = Curve.scale(p, s); + assert(!sp.infinity, 'expect nonzero'); + return sp; + }, + (p, s) => EllipticCurve.scale(s, p, Curve), + `${Curve.name} scale` + ); +} diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index ec5261fe66..1d07b14028 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -6,6 +6,7 @@ import { mod, } from '../../bindings/crypto/finite_field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; +import { Bool } from '../bool.js'; import { Unconstrained } from '../circuit_value.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; @@ -42,7 +43,9 @@ const ForeignField = { sub(x: Field3, y: Field3, f: bigint) { return sum([x, y], [-1n], f); }, - negate, + negate(x: Field3, f: bigint) { + return sum([Field3.from(0n), x], [-1n], f); + }, sum, Sum(x: Field3) { return new Sum(x); @@ -53,7 +56,24 @@ const ForeignField = { div: divide, assertMul, - assertAlmostFieldElements, + 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); + }, + + equals, }; /** @@ -219,16 +239,8 @@ function divide( multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); if (!allowZeroOverZero) { - // assert that y != 0 mod f by checking that it doesn't equal 0 or f - // this works because we assume y[2] <= f2 - // TODO is this the most efficient way? - let y01 = y[0].add(y[1].mul(1n << l)); - y01.equals(0n).and(y[2].equals(0n)).assertFalse(); - let [f0, f1, f2] = split(f); - let f01 = combine2([f0, f1]); - y01.equals(f01).and(y[2].equals(f2)).assertFalse(); + ForeignField.equals(y, 0n, f).assertFalse(); } - return z; } @@ -239,7 +251,8 @@ function assertMulInternal( x: Field3, y: Field3, xy: Field3 | Field2, - f: bigint + f: bigint, + message?: string ) { let { r01, r2, q } = multiplyNoRangeCheck(x, y, f); @@ -249,12 +262,12 @@ function assertMulInternal( // bind remainder to input xy if (xy.length === 2) { let [xy01, xy2] = xy; - r01.assertEquals(xy01); - r2.assertEquals(xy2); + r01.assertEquals(xy01, message); + r2.assertEquals(xy2, message); } else { let xy01 = xy[0].add(xy[1].mul(1n << l)); - r01.assertEquals(xy01); - r2.assertEquals(xy[2]); + r01.assertEquals(xy01, message); + r2.assertEquals(xy[2], message); } } @@ -363,6 +376,12 @@ function multiplyNoRangeCheck(a: Field3, b: Field3, f: bigint) { } function weakBound(x: Field, f: bigint) { + // if f0, f1 === 0, we can use a stronger bound x[2] < f2 + // because this is true for all field elements x in [0,f) + if ((f & l2Mask) === 0n) { + return x.add(lMask + 1n - (f >> l2)); + } + // otherwise, we use x[2] < f2 + 1, so we allow x[2] === f2 return x.add(lMask - (f >> l2)); } @@ -370,11 +389,11 @@ function weakBound(x: Field, f: bigint) { * Apply range checks and weak bounds checks to a list of Field3s. * Optimal if the list length is a multiple of 3. */ -function assertAlmostFieldElements(xs: Field3[], f: bigint) { +function assertAlmostReduced(xs: Field3[], f: bigint, skipMrc = false) { let bounds: Field[] = []; for (let x of xs) { - multiRangeCheck(x); + if (!skipMrc) multiRangeCheck(x); bounds.push(weakBound(x[2], f)); if (TupleN.hasLength(3, bounds)) { @@ -390,6 +409,43 @@ function assertAlmostFieldElements(xs: Field3[], f: bigint) { } } +/** + * check whether x = c mod f + * + * c is a constant, and we require c in [0, f) + * + * assumes that x is almost reduced mod f, so we know that x might be c or c + f, but not c + 2f, c + 3f, ... + */ +function equals(x: Field3, c: bigint, f: bigint) { + assert(c >= 0n && c < f, 'equals: c must be in [0, f)'); + + // constant case + if (Field3.isConstant(x)) { + return new Bool(mod(Field3.toBigint(x), f) === c); + } + + // provable case + if (f >= 1n << l2) { + // check whether x = 0 or x = f + let x01 = toVar(x[0].add(x[1].mul(1n << l))); + let [c01, c2] = [c & l2Mask, c >> l2]; + let [cPlusF01, cPlusF2] = [(c + f) & l2Mask, (c + f) >> l2]; + + // (x01, x2) = (c01, c2) + let isC = x01.equals(c01).and(x[2].equals(c2)); + // (x01, x2) = (cPlusF01, cPlusF2) + let isCPlusF = x01.equals(cPlusF01).and(x[2].equals(cPlusF2)); + + return isC.or(isCPlusF); + } else { + // if f < 2^2l, the approach above doesn't work (we don't know from x[2] = 0 that x < 2f), + // so in that case we assert that x < f and then check whether it's equal to c + ForeignField.assertLessThan(x, f); + let x012 = toVar(x[0].add(x[1].mul(1n << l)).add(x[2].mul(1n << l2))); + return x012.equals(c); + } +} + const Field3 = { /** * Turn a bigint into a 3-tuple of Fields @@ -470,7 +526,8 @@ function assertMul( x: Field3 | Sum, y: Field3 | Sum, xy: Field3 | Sum, - f: bigint + f: bigint, + message?: string ) { x = Sum.fromUnfinished(x); y = Sum.fromUnfinished(y); @@ -501,11 +558,14 @@ function assertMul( let x_ = Field3.toBigint(x0); let y_ = Field3.toBigint(y0); let xy_ = Field3.toBigint(xy0); - assert(mod(x_ * y_, f) === xy_, 'incorrect multiplication result'); + assert( + mod(x_ * y_, f) === xy_, + message ?? 'assertMul(): incorrect multiplication result' + ); return; } - assertMulInternal(x0, y0, xy0, f); + assertMulInternal(x0, y0, xy0, f, message); } class Sum { @@ -600,6 +660,9 @@ class Sum { let overflows: Field[] = []; let xRef = Unconstrained.witness(() => Field3.toBigint(xs[0])); + // this loop mirrors the computation that a chain of ffadd gates does, + // but everything is done only on the lowest limb and using generic gates. + // the output is a sequence of low limbs (x0) and overflows, which will be wired to the ffadd results at each step. for (let i = 0; i < n; i++) { // compute carry and overflow let [carry, overflow] = exists(2, () => { diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index 2e9c4732f3..135fca61a1 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -15,12 +15,14 @@ import { ZkProgram } from '../proof_system.js'; import { Provable } from '../provable.js'; import { assert } from './common.js'; import { + allConstant, and, constraintSystem, contains, equals, ifNotAllConstant, not, + or, repeat, withoutGenerics, } from '../testing/constraint-system.js'; @@ -72,6 +74,19 @@ for (let F of fields) { (x, y) => assertMulExample(x, y, F.modulus), 'assertMul' ); + // test for assertMul which mostly tests the negative case because for random inputs, we expect + // (x - y) * z != a + b + equivalentProvable({ from: [f, f, f, f, f], to: unit })( + (x, y, z, a, b) => assert(F.mul(F.sub(x, y), z) === F.add(a, b)), + (x, y, z, a, b) => + ForeignField.assertMul( + ForeignField.Sum(x).sub(y), + z, + ForeignField.Sum(a).add(b), + F.modulus + ), + 'assertMul negative' + ); // tests with inputs that aren't reduced mod f let big264 = unreducedForeignField(264, F); // this is the max size supported by our range checks / ffadd @@ -109,7 +124,7 @@ for (let F of fields) { equivalent({ from: [big264], to: unit })( (x) => assertWeakBound(x, F.modulus), - (x) => ForeignField.assertAlmostFieldElements([x], F.modulus) + (x) => ForeignField.assertAlmostReduced([x], F.modulus) ); // sumchain of 5 @@ -158,7 +173,7 @@ let ffProgram = ZkProgram({ mulWithBoundsCheck: { privateInputs: [Field3.provable, Field3.provable], method(x, y) { - ForeignField.assertAlmostFieldElements([x, y], F.modulus); + ForeignField.assertAlmostReduced([x, y], F.modulus); return ForeignField.mul(x, y, F.modulus); }, }, @@ -313,10 +328,13 @@ constraintSystem( 'assert mul', from2, (x, y) => assertMulExample(x, y, F.modulus), - and( - contains([addChain(1), addChain(1), addChainedIntoMul]), - // assertMul() doesn't use any range checks besides on internal values and the quotient - containsNTimes(2, mrc) + or( + and( + contains([addChain(2), addChain(2), addChainedIntoMul]), + // assertMul() doesn't use any range checks besides on internal values and the quotient + containsNTimes(2, mrc) + ), + allConstant ) ); diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index bcfc0a41b2..27cae55bf7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -4,14 +4,13 @@ import { compactMultiRangeCheck, multiRangeCheck, + rangeCheck16, rangeCheck64, + rangeCheck8, } from './range-check.js'; import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js'; -import { Field } from '../core.js'; +import { Field } from '../field.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; -import { Ecdsa, Point } from './elliptic-curve.js'; -import { CurveAffine } from '../../bindings/crypto/elliptic_curve.js'; -import { Crypto } from '../crypto.js'; export { Gadgets }; @@ -42,6 +41,25 @@ const Gadgets = { rangeCheck64(x: Field) { return rangeCheck64(x); }, + + /** + * Asserts that the input value is in the range [0, 2^16). + * + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. + */ + rangeCheck16(x: Field) { + return rangeCheck16(x); + }, + + /** + * Asserts that the input value is in the range [0, 2^8). + * + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. + */ + rangeCheck8(x: Field) { + return rangeCheck8(x); + }, + /** * A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript, * with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded. @@ -372,7 +390,7 @@ const Gadgets = { /** * Foreign field subtraction: `x - y mod f` * - * See {@link ForeignField.add} for assumptions and usage examples. + * See {@link Gadgets.ForeignField.add} for assumptions and usage examples. * * @throws fails if `x - y < -f`, where the result cannot be brought back to a positive number by adding `f` once. */ @@ -380,6 +398,17 @@ const Gadgets = { return ForeignField.sub(x, y, f); }, + /** + * Foreign field negation: `-x mod f = f - x` + * + * See {@link ForeignField.add} for assumptions and usage examples. + * + * @throws fails if `x > f`, where `f - x < 0`. + */ + neg(x: Field3, f: bigint) { + return ForeignField.negate(x, f); + }, + /** * Foreign field sum: `xs[0] + signs[0] * xs[1] + ... + signs[n-1] * xs[n] mod f` * @@ -390,7 +419,7 @@ const Gadgets = { * **Note**: For 3 or more inputs, `sum()` uses fewer constraints than a sequence of `add()` and `sub()` calls, * because we can avoid range checks on intermediate results. * - * See {@link ForeignField.add} for assumptions on inputs. + * See {@link Gadgets.ForeignField.add} for assumptions on inputs. * * @example * ```ts @@ -424,7 +453,7 @@ const Gadgets = { * To do this, we use an 88-bit range check on `2^88 - x[2] - (f[2] + 1)`, and same for y. * The implication is that x and y are _almost_ reduced modulo f. * - * All of the above assumptions are checked by {@link ForeignField.assertAlmostFieldElements}. + * All of the above assumptions are checked by {@link Gadgets.ForeignField.assertAlmostReduced}. * * **Warning**: This gadget does not add the extra bound check on the result. * So, to use the result in another foreign field multiplication, you have to add the bound check on it yourself, again. @@ -438,7 +467,7 @@ const Gadgets = { * let y = Provable.witness(Field3.provable, () => Field3.from(f - 2n)); * * // range check x, y and prove additional bounds x[2] <= f[2] - * ForeignField.assertAlmostFieldElements([x, y], f); + * ForeignField.assertAlmostReduced([x, y], f); * * // compute x * y mod f * let z = ForeignField.mul(x, y, f); @@ -453,7 +482,7 @@ const Gadgets = { /** * Foreign field inverse: `x^(-1) mod f` * - * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. * * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. */ @@ -464,11 +493,11 @@ const Gadgets = { /** * Foreign field division: `x * y^(-1) mod f` * - * See {@link ForeignField.mul} for assumptions on inputs and usage examples. + * See {@link Gadgets.ForeignField.mul} for assumptions on inputs and usage examples. * * This gadget adds an extra bound check on the result, so it can be used directly in another foreign field multiplication. * - * @throws Different than {@link ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. + * @throws Different than {@link Gadgets.ForeignField.mul}, this fails on unreduced input `x`, because it checks that `x === (x/y)*y` and the right side will be reduced. */ div(x: Field3, y: Field3, f: bigint) { return ForeignField.div(x, y, f); @@ -477,13 +506,13 @@ const Gadgets = { /** * Optimized multiplication of sums in a foreign field, for example: `(x - y)*z = a + b + c mod f` * - * Note: This is much more efficient than using {@link ForeignField.add} and {@link ForeignField.sub} separately to - * compute the multiplication inputs and outputs, and then using {@link ForeignField.mul} to constrain the result. + * Note: This is much more efficient than using {@link Gadgets.ForeignField.add} and {@link Gadgets.ForeignField.sub} separately to + * compute the multiplication inputs and outputs, and then using {@link Gadgets.ForeignField.mul} to constrain the result. * - * The sums passed into this gadgets are "lazy sums" created with {@link ForeignField.Sum}. + * The sums passed into this method are "lazy sums" created with {@link Gadgets.ForeignField.Sum}. * You can also pass in plain {@link Field3} elements. * - * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link ForeignField.mul}: + * **Assumptions**: The assumptions on the _summands_ are analogous to the assumptions described in {@link Gadgets.ForeignField.mul}: * - each summand's limbs are in the range [0, 2^88) * - summands that are part of a multiplication input satisfy `x[2] <= f[2]` * @@ -495,7 +524,7 @@ const Gadgets = { * @example * ```ts * // range-check x, y, z, a, b, c - * ForeignField.assertAlmostFieldElements([x, y, z], f); + * ForeignField.assertAlmostReduced([x, y, z], f); * Gadgets.multiRangeCheck(a); * Gadgets.multiRangeCheck(b); * Gadgets.multiRangeCheck(c); @@ -513,7 +542,7 @@ const Gadgets = { }, /** - * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + * Lazy sum of {@link Field3} elements, which can be used as input to {@link Gadgets.ForeignField.assertMul}. */ Sum(x: Field3) { return ForeignField.Sum(x); @@ -521,7 +550,7 @@ const Gadgets = { /** * Prove that each of the given {@link Field3} elements is "almost" reduced modulo f, - * i.e., satisfies the assumptions required by {@link ForeignField.mul} and other gadgets: + * i.e., satisfies the assumptions required by {@link Gadgets.ForeignField.mul} and other gadgets: * - each limb is in the range [0, 2^88) * - the most significant limb is less or equal than the modulus, x[2] <= f[2] * @@ -535,69 +564,46 @@ const Gadgets = { * let y = Provable.witness(Field3.provable, () => Field3.from(5n)); * let z = Provable.witness(Field3.provable, () => Field3.from(10n)); * - * ForeignField.assertAlmostFieldElements([x, y, z], f); + * ForeignField.assertAlmostReduced([x, y, z], f); * * // now we can use x, y, z as inputs to foreign field multiplication * let xy = ForeignField.mul(x, y, f); * let xyz = ForeignField.mul(xy, z, f); * * // since xy is an input to another multiplication, we need to prove that it is almost reduced again! - * ForeignField.assertAlmostFieldElements([xy], f); // TODO: would be more efficient to batch this with 2 other elements + * ForeignField.assertAlmostReduced([xy], f); // TODO: would be more efficient to batch this with 2 other elements * ``` */ - assertAlmostFieldElements(xs: Field3[], f: bigint) { - ForeignField.assertAlmostFieldElements(xs, f); + assertAlmostReduced(xs: Field3[], f: bigint, { skipMrc = false } = {}) { + ForeignField.assertAlmostReduced(xs, f, skipMrc); }, - }, - /** - * ECDSA verification gadget and helper methods. - */ - Ecdsa: { /** - * Verify an ECDSA signature. + * Prove that x < f for any constant f < 2^264. + * + * 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} + * and also uses more constraints; it should not be needed in most use cases. + * + * **Note**: This assumes that the limbs of x are in the range [0, 2^88), in contrast to + * {@link ForeignField.assertAlmostReduced} which adds that check itself. + * + * @throws if x is greater or equal to f. * * @example * ```ts - * const Curve = Crypto.createCurve(Crypto.CurveParams.Secp256k1); + * let x = Provable.witness(Field3.provable, () => Field3.from(0x1235n)); * - * // assert that message hash and signature are valid scalar field elements - * Gadgets.ForeignField.assertAlmostFieldElements( - * [signature.r, signature.s, msgHash], - * Curve.order - * ); - * // TODO add an easy way to prove that the public key lies on the curve + * // range check limbs of x + * Gadgets.multiRangeCheck(x); * - * // verify signature - * Gadgets.Ecdsa.verify(Curve, signature, msgHash, publicKey); + * // prove that x is fully reduced mod f + * Gadgets.ForeignField.assertLessThan(x, f); * ``` */ - verify( - Curve: CurveAffine, - signature: Ecdsa.Signature, - msgHash: Field3, - publicKey: Point - ) { - Ecdsa.verify(Curve, signature, msgHash, publicKey); + assertLessThan(x: Field3, f: bigint) { + ForeignField.assertLessThan(x, f); }, - - /** - * Sign a message hash using ECDSA. - * - * _This method is not provable._ - */ - sign( - Curve: Crypto.Curve, - msgHash: bigint, - privateKey: bigint - ): Ecdsa.signature { - return Ecdsa.sign(Curve, msgHash, privateKey); - }, - - /** - * Non-provable helper methods for interacting with ECDSA signatures. - */ - Signature: Ecdsa.Signature, }, /** @@ -616,19 +622,9 @@ export namespace Gadgets { export namespace ForeignField { /** - * Lazy sum of {@link Field3} elements, which can be used as input to {@link ForeignField.assertMul}. + * Lazy sum of {@link Field3} elements, which can be used as input to {@link Gadgets.ForeignField.assertMul}. */ export type Sum = Sum_; } - - export namespace Ecdsa { - /** - * ECDSA signature consisting of two curve scalars. - */ - export type Signature = EcdsaSignature; - export type signature = ecdsaSignature; - } } type Sum_ = Sum; -type EcdsaSignature = Ecdsa.Signature; -type ecdsaSignature = Ecdsa.signature; diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 1e44afdaf9..d53d8f5e51 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,8 +1,14 @@ import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { bitSlice, exists, toVar, toVars } from './common.js'; - -export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck }; +import { assert, bitSlice, exists, toVar, toVars } from './common.js'; + +export { + rangeCheck64, + rangeCheck8, + rangeCheck16, + multiRangeCheck, + compactMultiRangeCheck, +}; export { l, l2, l3, lMask, l2Mask }; /** @@ -207,3 +213,31 @@ function rangeCheck1Helper(inputs: { [z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0] ); } + +function rangeCheck16(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 16n, + `rangeCheck16: expected field to fit in 8 bits, got ${x}` + ); + return; + } + // check that x fits in 16 bits + x.rangeCheckHelper(16).assertEquals(x); +} + +function rangeCheck8(x: Field) { + if (x.isConstant()) { + assert( + x.toBigInt() < 1n << 8n, + `rangeCheck8: expected field to fit in 8 bits, got ${x}` + ); + return; + } + + // check that x fits in 16 bits + x.rangeCheckHelper(16).assertEquals(x); + // check that 2^8 x fits in 16 bits + let x256 = x.mul(1 << 8).seal(); + x256.rangeCheckHelper(16).assertEquals(x256); +} diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 47aafbf592..1caea18f17 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -40,6 +40,13 @@ constraintSystem( ifNotAllConstant(withoutGenerics(equals(['RangeCheck0']))) ); +constraintSystem( + 'range check 8', + { from: [Field] }, + Gadgets.rangeCheck8, + ifNotAllConstant(withoutGenerics(equals(['EndoMulScalar', 'EndoMulScalar']))) +); + constraintSystem( 'multi-range check', { from: [Field, Field, Field] }, @@ -72,6 +79,12 @@ let RangeCheck = ZkProgram({ Gadgets.rangeCheck64(x); }, }, + check8: { + privateInputs: [Field], + method(x) { + Gadgets.rangeCheck8(x); + }, + }, checkMulti: { privateInputs: [Field, Field, Field], method(x, y, z) { @@ -91,8 +104,9 @@ let RangeCheck = ZkProgram({ await RangeCheck.compile(); // TODO: we use this as a test because there's no way to check custom gates quickly :( +const runs = 2; -await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( +await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs })( (x) => { assert(x < 1n << 64n); return true; @@ -103,9 +117,20 @@ await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })( } ); +await equivalentAsync({ from: [maybeUint(8)], to: boolean }, { runs })( + (x) => { + assert(x < 1n << 8n); + return true; + }, + async (x) => { + let proof = await RangeCheck.check8(x); + return await RangeCheck.verify(proof); + } +); + await equivalentAsync( { from: [maybeUint(l), uint(l), uint(l)], to: boolean }, - { runs: 3 } + { runs } )( (x, y, z) => { assert(!(x >> l) && !(y >> l) && !(z >> l), 'multi: not out of range'); @@ -119,7 +144,7 @@ await equivalentAsync( await equivalentAsync( { from: [maybeUint(2n * l), uint(l)], to: boolean }, - { runs: 3 } + { runs } )( (xy, z) => { assert(!(xy >> (2n * l)) && !(z >> l), 'compact: not out of range'); diff --git a/src/lib/gadgets/test-utils.ts b/src/lib/gadgets/test-utils.ts index 309dac6161..96c78866e3 100644 --- a/src/lib/gadgets/test-utils.ts +++ b/src/lib/gadgets/test-utils.ts @@ -1,10 +1,17 @@ import type { FiniteField } from '../../bindings/crypto/finite_field.js'; -import { ProvableSpec } from '../testing/equivalent.js'; +import { ProvableSpec, spec } from '../testing/equivalent.js'; import { Random } from '../testing/random.js'; import { Gadgets } from './gadgets.js'; import { assert } from './common.js'; +import { Bytes } from '../provable-types/provable-types.js'; -export { foreignField, unreducedForeignField, uniformForeignField, throwError }; +export { + foreignField, + unreducedForeignField, + uniformForeignField, + bytes, + throwError, +}; const { Field3 } = Gadgets; @@ -49,6 +56,16 @@ function uniformForeignField( }; } +function bytes(length: number) { + const Bytes_ = Bytes(length); + return spec({ + rng: Random.map(Random.bytes(length), (x) => Uint8Array.from(x)), + there: Bytes_.from, + back: (x) => x.toBytes(), + provable: Bytes_.provable, + }); +} + // helper function throwError(message: string): T { diff --git a/src/lib/gates.ts b/src/lib/gates.ts index 5d620066c5..a8900cfe49 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,5 +1,6 @@ -import { KimchiGateType, Snarky } from '../snarky.js'; +import { Snarky } from '../snarky.js'; import { FieldConst, type Field } from './field.js'; +import { exists } from './gadgets/common.js'; import { MlArray, MlTuple } from './ml/base.js'; import { TupleN } from './util/types.js'; @@ -13,6 +14,7 @@ export { generic, foreignFieldAdd, foreignFieldMul, + KimchiGateType, }; const Gates = { @@ -150,7 +152,7 @@ function generic( } function zero(a: Field, b: Field, c: Field) { - Snarky.gates.zero(a.value, b.value, c.value); + raw(KimchiGateType.Zero, [a, b, c], []); } /** @@ -234,9 +236,32 @@ function foreignFieldMul(inputs: { } function raw(kind: KimchiGateType, values: Field[], coefficients: bigint[]) { + let n = values.length; + let padding = exists(15 - n, () => Array(15 - n).fill(0n)); Snarky.gates.raw( kind, - MlArray.to(values.map((x) => x.value)), + MlArray.to(values.concat(padding).map((x) => x.value)), MlArray.to(coefficients.map(FieldConst.fromBigint)) ); } + +enum KimchiGateType { + Zero, + Generic, + Poseidon, + CompleteAdd, + VarBaseMul, + EndoMul, + EndoMulScalar, + Lookup, + CairoClaim, + CairoInstruction, + CairoFlags, + CairoTransition, + RangeCheck0, + RangeCheck1, + ForeignFieldAdd, + ForeignFieldMul, + Xor16, + Rot64, +} diff --git a/src/lib/group.ts b/src/lib/group.ts index 89cf5bf249..67b021097e 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -1,8 +1,8 @@ -import { Field, FieldVar, isField } from './field.js'; +import { Field, FieldVar } from './field.js'; import { Scalar } from './scalar.js'; import { Snarky } from '../snarky.js'; import { Field as Fp } from '../provable/field-bigint.js'; -import { Pallas } from '../bindings/crypto/elliptic_curve.js'; +import { GroupAffine, Pallas } from '../bindings/crypto/elliptic_curve.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; @@ -48,10 +48,10 @@ class Group { x: FieldVar | Field | number | string | bigint; y: FieldVar | Field | number | string | bigint; }) { - this.x = isField(x) ? x : new Field(x); - this.y = isField(y) ? y : new Field(y); + this.x = x instanceof Field ? x : new Field(x); + this.y = y instanceof Field ? y : new Field(y); - if (this.#isConstant()) { + if (isConstant(this)) { // we also check the zero element (0, 0) here if (this.x.equals(0).and(this.y.equals(0)).toBoolean()) return; @@ -72,39 +72,6 @@ class Group { } } - // helpers - static #fromAffine({ - x, - y, - infinity, - }: { - x: bigint; - y: bigint; - infinity: boolean; - }) { - return infinity ? Group.zero : new Group({ x, y }); - } - - static #fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { - return this.#fromAffine(Pallas.toAffine({ x, y, z })); - } - - #toTuple(): [0, FieldVar, FieldVar] { - return [0, this.x.value, this.y.value]; - } - - #isConstant() { - return this.x.isConstant() && this.y.isConstant(); - } - - #toProjective() { - return Pallas.fromAffine({ - x: this.x.toBigInt(), - y: this.y.toBigInt(), - infinity: false, - }); - } - /** * Checks if this element is the `zero` element `{x: 0, y: 0}`. */ @@ -122,15 +89,15 @@ class Group { * ``` */ add(g: Group) { - if (this.#isConstant() && g.#isConstant()) { + if (isConstant(this) && isConstant(g)) { // we check if either operand is zero, because adding zero to g just results in g (and vise versa) if (this.isZero().toBoolean()) { return g; } else if (g.isZero().toBoolean()) { return this; } else { - let g_proj = Pallas.add(this.#toProjective(), g.#toProjective()); - return Group.#fromProjective(g_proj); + let g_proj = Pallas.add(toProjective(this), toProjective(g)); + return fromProjective(g_proj); } } else { const { x: x1, y: y1 } = this; @@ -171,9 +138,9 @@ class Group { }); let [, x, y] = Snarky.gates.ecAdd( - Group.from(x1.seal(), y1.seal()).#toTuple(), - Group.from(x2.seal(), y2.seal()).#toTuple(), - Group.from(x3, y3).#toTuple(), + 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, @@ -224,13 +191,13 @@ class Group { scale(s: Scalar | number | bigint) { let scalar = Scalar.from(s); - if (this.#isConstant() && scalar.isConstant()) { - let g_proj = Pallas.scale(this.#toProjective(), scalar.toBigInt()); - return Group.#fromProjective(g_proj); + if (isConstant(this) && scalar.isConstant()) { + let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); + return fromProjective(g_proj); } else { let [, ...bits] = scalar.value; bits.reverse(); - let [, x, y] = Snarky.group.scale(this.#toTuple(), [0, ...bits]); + let [, x, y] = Snarky.group.scale(toTuple(this), [0, ...bits]); return new Group({ x, y }); } } @@ -456,3 +423,29 @@ class Group { } } } + +// internal helpers + +function isConstant(g: Group) { + return g.x.isConstant() && g.y.isConstant(); +} + +function toTuple(g: Group): [0, FieldVar, FieldVar] { + return [0, g.x.value, g.y.value]; +} + +function toProjective(g: Group) { + return Pallas.fromAffine({ + x: g.x.toBigInt(), + y: g.y.toBigInt(), + infinity: false, + }); +} + +function fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { + return fromAffine(Pallas.toAffine({ x, y, z })); +} + +function fromAffine({ x, y, infinity }: GroupAffine) { + return infinity ? Group.zero : new Group({ x, y }); +} diff --git a/src/lib/hash-generic.ts b/src/lib/hash-generic.ts index c5e240ae0a..7e76c8ceee 100644 --- a/src/lib/hash-generic.ts +++ b/src/lib/hash-generic.ts @@ -1,4 +1,4 @@ -import { GenericField } from '../bindings/lib/generic.js'; +import { GenericSignableField } from '../bindings/lib/generic.js'; import { prefixToField } from '../bindings/lib/binable.js'; export { createHashHelpers, HashHelpers }; @@ -11,7 +11,7 @@ type Hash = { type HashHelpers = ReturnType>; function createHashHelpers( - Field: GenericField, + Field: GenericSignableField, Hash: Hash ) { function salt(prefix: string) { diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 684b7632ce..39fd993774 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -13,7 +13,7 @@ export { Poseidon, TokenSymbol }; // internal API export { HashInput, - Hash, + HashHelpers, emptyHashWithPrefix, hashWithPrefix, salt, @@ -23,19 +23,19 @@ export { }; class Sponge { - private sponge: unknown; + #sponge: unknown; constructor() { let isChecked = Provable.inCheckedComputation(); - this.sponge = Snarky.poseidon.sponge.create(isChecked); + this.#sponge = Snarky.poseidon.sponge.create(isChecked); } absorb(x: Field) { - Snarky.poseidon.sponge.absorb(this.sponge, x.value); + Snarky.poseidon.sponge.absorb(this.#sponge, x.value); } squeeze(): Field { - return Field(Snarky.poseidon.sponge.squeeze(this.sponge)); + return Field(Snarky.poseidon.sponge.squeeze(this.#sponge)); } } @@ -105,8 +105,8 @@ function hashConstant(input: Field[]) { return Field(PoseidonBigint.hash(toBigints(input))); } -const Hash = createHashHelpers(Field, Poseidon); -let { salt, emptyHashWithPrefix, hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { salt, emptyHashWithPrefix, hashWithPrefix } = HashHelpers; // same as Random_oracle.prefix_to_field in OCaml function prefixToField(prefix: string) { @@ -179,12 +179,11 @@ const TokenSymbolPure: ProvableExtended< toInput({ field }) { return { packed: [[field, 48]] }; }, + empty() { + return { symbol: '', field: Field(0n) }; + }, }; class TokenSymbol extends Struct(TokenSymbolPure) { - static get empty() { - return { symbol: '', field: Field(0) }; - } - static from(symbol: string): TokenSymbol { let bytesLength = new TextEncoder().encode(symbol).length; if (bytesLength > 6) diff --git a/src/lib/hashes-combined.ts b/src/lib/hashes-combined.ts new file mode 100644 index 0000000000..8440a25b32 --- /dev/null +++ b/src/lib/hashes-combined.ts @@ -0,0 +1,127 @@ +import { Poseidon } from './hash.js'; +import { Keccak } from './keccak.js'; +import { Bytes } from './provable-types/provable-types.js'; + +export { Hash }; + +/** + * A collection of hash functions which can be used in provable code. + */ +const Hash = { + /** + * Hashes the given field elements using [Poseidon](https://eprint.iacr.org/2019/458.pdf). Alias for `Poseidon.hash()`. + * + * ```ts + * let hash = Hash.hash([a, b, c]); + * ``` + * + * **Important:** This is by far the most efficient hash function o1js has available in provable code. + * Use it by default, if no compatibility concerns require you to use a different hash function. + * + * The Poseidon implementation operates over the native [Pallas base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) + * and uses parameters generated specifically for the [Mina](https://minaprotocol.com) blockchain. + * + * We use a `rate` of 2, which means that 2 field elements are hashed per permutation. + * A permutation causes 11 rows in the constraint system. + * + * You can find the full set of Poseidon parameters [here](https://github.com/o1-labs/o1js-bindings/blob/main/crypto/constants.ts). + */ + hash: Poseidon.hash, + + /** + * The [Poseidon](https://eprint.iacr.org/2019/458.pdf) hash function. + * + * See {@link Hash.hash} for details and usage examples. + */ + Poseidon, + + /** + * The SHA3 hash function with an output length of 256 bits. + */ + SHA3_256: { + /** + * Hashes the given bytes using SHA3-256. + * + * This is an alias for `Keccak.nistSha3(256, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(256, bytes); + }, + }, + + /** + * The SHA3 hash function with an output length of 384 bits. + */ + SHA3_384: { + /** + * Hashes the given bytes using SHA3-384. + * + * This is an alias for `Keccak.nistSha3(384, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(384, bytes); + }, + }, + + /** + * The SHA3 hash function with an output length of 512 bits. + */ + SHA3_512: { + /** + * Hashes the given bytes using SHA3-512. + * + * This is an alias for `Keccak.nistSha3(512, bytes)`.\ + * See {@link Keccak.nistSha3} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.nistSha3(512, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 256 bits. + */ + Keccak256: { + /** + * Hashes the given bytes using Keccak-256. + * + * This is an alias for `Keccak.preNist(256, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(256, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 384 bits. + */ + Keccak384: { + /** + * Hashes the given bytes using Keccak-384. + * + * This is an alias for `Keccak.preNist(384, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(384, bytes); + }, + }, + + /** + * The pre-NIST Keccak hash function with an output length of 512 bits. + */ + Keccak512: { + /** + * Hashes the given bytes using Keccak-512. + * + * This is an alias for `Keccak.preNist(512, bytes)`.\ + * See {@link Keccak.preNist} for details and usage examples. + */ + hash(bytes: Bytes) { + return Keccak.preNist(512, bytes); + }, + }, +}; diff --git a/src/lib/int.test.ts b/src/lib/int.test.ts index cc93726496..1914a9fede 100644 --- a/src/lib/int.test.ts +++ b/src/lib/int.test.ts @@ -1,28 +1,15 @@ import { - isReady, Provable, - shutdown, Int64, UInt64, UInt32, + UInt8, Field, Bool, Sign, } from 'o1js'; describe('int', () => { - beforeAll(async () => { - await isReady; - }); - - afterAll(async () => { - // Use a timeout to defer the execution of `shutdown()` until Jest processes all tests. - // `shutdown()` exits the process when it's done cleanup so we want to delay it's execution until Jest is done - setTimeout(async () => { - await shutdown(); - }, 0); - }); - const NUMBERMAX = 2 ** 53 - 1; // JavaScript numbers can only safely store integers in the range -(2^53 − 1) to 2^53 − 1 describe('Int64', () => { @@ -2150,4 +2137,850 @@ describe('int', () => { }); }); }); + + describe('UInt8', () => { + const NUMBERMAX = UInt8.MAXINT().value; + + describe('Inside circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.add(y).assertEquals(2); + }); + }).not.toThrow(); + }); + + it('100+100=200', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.add(y).assertEquals(new UInt8(200)); + }); + }).not.toThrow(); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const n = ((1n << 8n) - 2n) / 2n; + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(n)); + const y = Provable.witness(UInt8, () => new UInt8(n)); + x.add(y).add(1).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on overflow addition', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.add(y); + }); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.sub(y).assertEquals(new UInt8(0)); + }); + }).not.toThrow(); + }); + + it('100-50=50', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(50)); + x.sub(y).assertEquals(new UInt8(50)); + }); + }).not.toThrow(); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.sub(y); + }); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.mul(y).assertEquals(new UInt8(2)); + }); + }).not.toThrow(); + }); + + it('1x0=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mul(y).assertEquals(new UInt8(0)); + }); + }).not.toThrow(); + }); + + it('12x20=240', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(12)); + const y = Provable.witness(UInt8, () => new UInt8(20)); + x.mul(y).assertEquals(new UInt8(240)); + }); + }).not.toThrow(); + }); + + it('MAXINTx1=MAXINT', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.mul(y).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.mul(y); + }); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(2)); + }); + }).not.toThrow(); + }); + + it('0/1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(0)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(new UInt8(0)); + }); + }).not.toThrow(); + }); + + it('20/10=2', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(20)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.div(y).assertEquals(new UInt8(2)); + }); + }).not.toThrow(); + }); + + it('MAXINT/1=MAXINT', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.div(y).assertEquals(UInt8.MAXINT()); + }); + }).not.toThrow(); + }); + + it('should throw on division by zero', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.div(y); + }); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.mod(y).assertEquals(new UInt8(0)); + }); + }).not.toThrow(); + }); + + it('50%32=18', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(50)); + const y = Provable.witness(UInt8, () => new UInt8(32)); + x.mod(y).assertEquals(new UInt8(18)); + }); + }).not.toThrow(); + }); + + it('MAXINT%7=3', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(7)); + x.mod(y).assertEquals(new UInt8(3)); + }); + }).not.toThrow(); + }); + + it('should throw on mod by zero', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => new UInt8(0)); + x.mod(y).assertEquals(new UInt8(1)); + }); + }).toThrow(); + }); + }); + + describe('assertLt', () => { + it('1<2=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertLessThan(y); + }); + }).not.toThrow(); + }); + + it('1<1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('2<1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('10<100=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertLessThan(y); + }); + }).not.toThrow(); + }); + + it('100<10=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertLessThan(y); + }); + }).toThrow(); + }); + + it('MAXINT { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThan(y); + }); + }).toThrow(); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertLessThanOrEqual(y); + }); + }).toThrow(); + }); + + it('10<=100=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('100<=10=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertLessThanOrEqual(y); + }); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertLessThanOrEqual(y); + }); + }).not.toThrow(); + }); + }); + + describe('assertGreaterThan', () => { + it('2>1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(2)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThan(y); + }); + }).not.toThrow(); + }); + + it('1>1=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('1>2=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('100>10=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertGreaterThan(y); + }); + }).not.toThrow(); + }); + + it('10>100=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1000)); + const y = Provable.witness(UInt8, () => new UInt8(100000)); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThan(y); + }); + }).toThrow(); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('1>=2=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(1)); + const y = Provable.witness(UInt8, () => new UInt8(2)); + x.assertGreaterThanOrEqual(y); + }); + }).toThrow(); + }); + + it('100>=10=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(100)); + const y = Provable.witness(UInt8, () => new UInt8(10)); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + + it('10>=100=false', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => new UInt8(10)); + const y = Provable.witness(UInt8, () => new UInt8(100)); + x.assertGreaterThanOrEqual(y); + }); + }).toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.MAXINT()); + const y = Provable.witness(UInt8, () => UInt8.MAXINT()); + x.assertGreaterThanOrEqual(y); + }); + }).not.toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + expect(() => { + Provable.runAndCheck(() => { + const x = Provable.witness(UInt8, () => UInt8.from(1)); + const y = Provable.witness(UInt8, () => new UInt8(1)); + x.assertEquals(y); + }); + }).not.toThrow(); + }); + }); + }); + }); + + describe('Outside of circuit', () => { + describe('add', () => { + it('1+1=2', () => { + expect(new UInt8(1).add(1).toString()).toEqual('2'); + }); + + it('50+50=100', () => { + expect(new UInt8(50).add(50).toString()).toEqual('100'); + }); + + it('(MAXINT/2+MAXINT/2) adds to MAXINT', () => { + const value = ((1n << 8n) - 2n) / 2n; + expect( + new UInt8(value).add(new UInt8(value)).add(new UInt8(1)).toString() + ).toEqual(UInt8.MAXINT().toString()); + }); + + it('should throw on overflow addition', () => { + expect(() => { + UInt8.MAXINT().add(1); + }).toThrow(); + }); + }); + + describe('sub', () => { + it('1-1=0', () => { + expect(new UInt8(1).sub(1).toString()).toEqual('0'); + }); + + it('100-50=50', () => { + expect(new UInt8(100).sub(50).toString()).toEqual('50'); + }); + + it('should throw on sub if results in negative number', () => { + expect(() => { + UInt8.from(0).sub(1); + }).toThrow(); + }); + }); + + describe('mul', () => { + it('1x2=2', () => { + expect(new UInt8(1).mul(2).toString()).toEqual('2'); + }); + + it('1x0=0', () => { + expect(new UInt8(1).mul(0).toString()).toEqual('0'); + }); + + it('12x20=240', () => { + expect(new UInt8(12).mul(20).toString()).toEqual('240'); + }); + + it('MAXINTx1=MAXINT', () => { + expect(UInt8.MAXINT().mul(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on overflow multiplication', () => { + expect(() => { + UInt8.MAXINT().mul(2); + }).toThrow(); + }); + }); + + describe('div', () => { + it('2/1=2', () => { + expect(new UInt8(2).div(1).toString()).toEqual('2'); + }); + + it('0/1=0', () => { + expect(new UInt8(0).div(1).toString()).toEqual('0'); + }); + + it('20/10=2', () => { + expect(new UInt8(20).div(10).toString()).toEqual('2'); + }); + + it('MAXINT/1=MAXINT', () => { + expect(UInt8.MAXINT().div(1).toString()).toEqual( + UInt8.MAXINT().toString() + ); + }); + + it('should throw on division by zero', () => { + expect(() => { + UInt8.MAXINT().div(0); + }).toThrow(); + }); + }); + + describe('mod', () => { + it('1%1=0', () => { + expect(new UInt8(1).mod(1).toString()).toEqual('0'); + }); + + it('50%32=18', () => { + expect(new UInt8(50).mod(32).toString()).toEqual('18'); + }); + + it('MAXINT%7=3', () => { + expect(UInt8.MAXINT().mod(7).toString()).toEqual('3'); + }); + + it('should throw on mod by zero', () => { + expect(() => { + UInt8.MAXINT().mod(0); + }).toThrow(); + }); + }); + + describe('lessThan', () => { + it('1<2=true', () => { + expect(new UInt8(1).lessThan(new UInt8(2))).toEqual(Bool(true)); + }); + + it('1<1=false', () => { + expect(new UInt8(1).lessThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('2<1=false', () => { + expect(new UInt8(2).lessThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('10<100=true', () => { + expect(new UInt8(10).lessThan(new UInt8(100))).toEqual(Bool(true)); + }); + + it('100<10=false', () => { + expect(new UInt8(100).lessThan(new UInt8(10))).toEqual(Bool(false)); + }); + + it('MAXINT { + expect(UInt8.MAXINT().lessThan(UInt8.MAXINT())).toEqual(Bool(false)); + }); + }); + + describe('lessThanOrEqual', () => { + it('1<=1=true', () => { + expect(new UInt8(1).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('2<=1=false', () => { + expect(new UInt8(2).lessThanOrEqual(new UInt8(1))).toEqual( + Bool(false) + ); + }); + + it('10<=100=true', () => { + expect(new UInt8(10).lessThanOrEqual(new UInt8(100))).toEqual( + Bool(true) + ); + }); + + it('100<=10=false', () => { + expect(new UInt8(100).lessThanOrEqual(new UInt8(10))).toEqual( + Bool(false) + ); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(UInt8.MAXINT().lessThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertLessThanOrEqual', () => { + it('1<=1=true', () => { + expect(() => { + new UInt8(1).assertLessThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('2<=1=false', () => { + expect(() => { + new UInt8(2).assertLessThanOrEqual(new UInt8(1)); + }).toThrow(); + }); + + it('10<=100=true', () => { + expect(() => { + new UInt8(10).assertLessThanOrEqual(new UInt8(100)); + }).not.toThrow(); + }); + + it('100<=10=false', () => { + expect(() => { + new UInt8(100).assertLessThanOrEqual(new UInt8(10)); + }).toThrow(); + }); + + it('MAXINT<=MAXINT=true', () => { + expect(() => { + UInt8.MAXINT().assertLessThanOrEqual(UInt8.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('greaterThan', () => { + it('2>1=true', () => { + expect(new UInt8(2).greaterThan(new UInt8(1))).toEqual(Bool(true)); + }); + + it('1>1=false', () => { + expect(new UInt8(1).greaterThan(new UInt8(1))).toEqual(Bool(false)); + }); + + it('1>2=false', () => { + expect(new UInt8(1).greaterThan(new UInt8(2))).toEqual(Bool(false)); + }); + + it('100>10=true', () => { + expect(new UInt8(100).greaterThan(new UInt8(10))).toEqual(Bool(true)); + }); + + it('10>100=false', () => { + expect(new UInt8(10).greaterThan(new UInt8(100))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>MAXINT=false', () => { + expect(UInt8.MAXINT().greaterThan(UInt8.MAXINT())).toEqual( + Bool(false) + ); + }); + }); + + describe('assertGreaterThan', () => { + it('1>1=false', () => { + expect(() => { + new UInt8(1).assertGreaterThan(new UInt8(1)); + }).toThrow(); + }); + + it('2>1=true', () => { + expect(() => { + new UInt8(2).assertGreaterThan(new UInt8(1)); + }).not.toThrow(); + }); + + it('10>100=false', () => { + expect(() => { + new UInt8(10).assertGreaterThan(new UInt8(100)); + }).toThrow(); + }); + + it('100000>1000=true', () => { + expect(() => { + new UInt8(100).assertGreaterThan(new UInt8(10)); + }).not.toThrow(); + }); + + it('MAXINT>MAXINT=false', () => { + expect(() => { + UInt8.MAXINT().assertGreaterThan(UInt8.MAXINT()); + }).toThrow(); + }); + }); + + describe('greaterThanOrEqual', () => { + it('2>=1=true', () => { + expect(new UInt8(2).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('1>=1=true', () => { + expect(new UInt8(1).greaterThanOrEqual(new UInt8(1))).toEqual( + Bool(true) + ); + }); + + it('1>=2=false', () => { + expect(new UInt8(1).greaterThanOrEqual(new UInt8(2))).toEqual( + Bool(false) + ); + }); + + it('100>=10=true', () => { + expect(new UInt8(100).greaterThanOrEqual(new UInt8(10))).toEqual( + Bool(true) + ); + }); + + it('10>=100=false', () => { + expect(new UInt8(10).greaterThanOrEqual(new UInt8(100))).toEqual( + Bool(false) + ); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(UInt8.MAXINT().greaterThanOrEqual(UInt8.MAXINT())).toEqual( + Bool(true) + ); + }); + }); + + describe('assertGreaterThanOrEqual', () => { + it('1>=1=true', () => { + expect(() => { + new UInt8(1).assertGreaterThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('2>=1=true', () => { + expect(() => { + new UInt8(2).assertGreaterThanOrEqual(new UInt8(1)); + }).not.toThrow(); + }); + + it('10>=100=false', () => { + expect(() => { + new UInt8(10).assertGreaterThanOrEqual(new UInt8(100)); + }).toThrow(); + }); + + it('100>=10=true', () => { + expect(() => { + new UInt8(100).assertGreaterThanOrEqual(new UInt8(10)); + }).not.toThrow(); + }); + + it('MAXINT>=MAXINT=true', () => { + expect(() => { + UInt32.MAXINT().assertGreaterThanOrEqual(UInt32.MAXINT()); + }).not.toThrow(); + }); + }); + + describe('toString()', () => { + it('should be the same as Field(0)', async () => { + const x = new UInt8(0); + const y = Field(0); + expect(x.toString()).toEqual(y.toString()); + }); + it('should be the same as 2^8-1', async () => { + const x = new UInt8(NUMBERMAX.toBigInt()); + const y = Field(String(NUMBERMAX)); + expect(x.toString()).toEqual(y.toString()); + }); + }); + + describe('check()', () => { + it('should pass checking a MAXINT', () => { + expect(() => { + UInt8.check(UInt8.MAXINT()); + }).not.toThrow(); + }); + + it('should throw checking over MAXINT', () => { + const x = UInt8.MAXINT(); + expect(() => { + UInt8.check(x.add(1)); + }).toThrow(); + }); + }); + + describe('from() ', () => { + describe('fromNumber()', () => { + it('should be the same as Field(1)', () => { + const x = UInt8.from(1); + expect(x.value).toEqual(Field(1)); + }); + + it('should be the same as 2^53-1', () => { + const x = UInt8.from(NUMBERMAX); + expect(x.value).toEqual(NUMBERMAX); + }); + }); + }); + }); + }); }); diff --git a/src/lib/int.ts b/src/lib/int.ts index a48118fa2c..13c2553101 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,11 +1,12 @@ import { Field, Bool } from './core.js'; -import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; +import { AnyConstructor, CircuitValue, Struct, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; - +import { Gadgets } from './gadgets/gadgets.js'; +import { FieldVar, withMessage } from './field.js'; // external API -export { UInt32, UInt64, Int64, Sign }; +export { UInt8, UInt32, UInt64, Int64, Sign }; /** * A 64 bit unsigned integer with values ranging from 0 to 18,446,744,073,709,551,615. @@ -723,8 +724,8 @@ class Sign extends CircuitValue { // x^2 === 1 <=> x === 1 or x === -1 x.value.square().assertEquals(Field(1)); } - static emptyValue(): Sign { - return Sign.one; + static empty(): InstanceType { + return Sign.one as any; } static toInput(x: Sign): HashInput { return { packed: [[x.isPositive().toField(), 1]] }; @@ -962,3 +963,387 @@ class Int64 extends CircuitValue implements BalanceChange { return this.sgn.isPositive(); } } + +/** + * A 8 bit unsigned integer with values ranging from 0 to 255. + */ +class UInt8 extends Struct({ + value: Field, +}) { + static NUM_BITS = 8; + + /** + * Create a {@link UInt8} from a bigint or number. + * The max value of a {@link UInt8} is `2^8 - 1 = 255`. + * + * **Warning**: Cannot overflow past 255, an error is thrown if the result is greater than 255. + */ + constructor(x: number | bigint | FieldVar | UInt8) { + if (x instanceof UInt8) x = x.value.value; + super({ value: Field(x) }); + UInt8.checkConstant(this.value); + } + + static Unsafe = { + /** + * Create a {@link UInt8} from a {@link Field} without constraining its range. + * + * **Warning**: This is unsafe, because it does not prove that the input {@link Field} actually fits in 8 bits.\ + * Only use this if you know what you are doing, otherwise use the safe {@link UInt8.from}. + */ + fromField(x: Field) { + return new UInt8(x.value); + }, + }; + + /** + * Add a {@link UInt8} to another {@link UInt8} without allowing overflow. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const sum = x.add(5); + * sum.assertEquals(8); + * ``` + * + * @throws if the result is greater than 255. + */ + add(y: UInt8 | bigint | number) { + let z = this.value.add(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Subtract a {@link UInt8} from another {@link UInt8} without allowing underflow. + * + * @example + * ```ts + * const x = UInt8.from(8); + * const difference = x.sub(5); + * difference.assertEquals(3); + * ``` + * + * @throws if the result is less than 0. + */ + sub(y: UInt8 | bigint | number) { + let z = this.value.sub(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Multiply a {@link UInt8} by another {@link UInt8} without allowing overflow. + * + * @example + * ```ts + * const x = UInt8.from(3); + * const product = x.mul(5); + * product.assertEquals(15); + * ``` + * + * @throws if the result is greater than 255. + */ + mul(y: UInt8 | bigint | number) { + let z = this.value.mul(UInt8.from(y).value); + Gadgets.rangeCheck8(z); + return UInt8.Unsafe.fromField(z); + } + + /** + * Divide a {@link UInt8} by another {@link UInt8}. + * This is integer division that rounds down. + * + * @example + * ```ts + * const x = UInt8.from(7); + * const quotient = x.div(2); + * quotient.assertEquals(3); + * ``` + */ + div(y: UInt8 | bigint | number) { + return this.divMod(y).quotient; + } + + /** + * Get the remainder a {@link UInt8} of division of another {@link UInt8}. + * + * @example + * ```ts + * const x = UInt8.from(50); + * const mod = x.mod(30); + * mod.assertEquals(20); + * ``` + */ + mod(y: UInt8 | bigint | number) { + return this.divMod(y).remainder; + } + + /** + * Get the quotient and remainder of a {@link UInt8} divided by another {@link UInt8}: + * + * `x == y * q + r`, where `0 <= r < y`. + * + * @param y - a {@link UInt8} to get the quotient and remainder of another {@link UInt8}. + * + * @return The quotient `q` and remainder `r`. + */ + divMod(y: UInt8 | bigint | number) { + let x = this.value; + let y_ = UInt8.from(y).value.seal(); + + if (this.value.isConstant() && y_.isConstant()) { + let xn = x.toBigInt(); + let yn = y_.toBigInt(); + let q = xn / yn; + let r = xn - q * yn; + return { quotient: UInt8.from(q), remainder: UInt8.from(r) }; + } + + // prove that x === q * y + r, where 0 <= r < y + let q = Provable.witness(Field, () => Field(x.toBigInt() / y_.toBigInt())); + let r = x.sub(q.mul(y_)).seal(); + + // q, r being 16 bits is enough for them to be 8 bits, + // thanks to the === x check and the r < y check below + Gadgets.rangeCheck16(q); + Gadgets.rangeCheck16(r); + + let remainder = UInt8.Unsafe.fromField(r); + let quotient = UInt8.Unsafe.fromField(q); + + remainder.assertLessThan(y); + return { quotient, remainder }; + } + + /** + * Check if this {@link UInt8} is less than or equal to another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * UInt8.from(3).lessThanOrEqual(UInt8.from(5)); + * ``` + */ + lessThanOrEqual(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() <= y_.toBigInt()); + } + throw Error('Not implemented'); + } + + /** + * Check if this {@link UInt8} is less than another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * UInt8.from(2).lessThan(UInt8.from(3)); + * ``` + */ + lessThan(y: UInt8 | bigint | number): Bool { + let y_ = UInt8.from(y); + if (this.value.isConstant() && y_.value.isConstant()) { + return Bool(this.toBigInt() < y_.toBigInt()); + } + throw Error('Not implemented'); + } + + /** + * Assert that this {@link UInt8} is less than another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + 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; + } + // x < y <=> x + 1 <= y + let xPlus1 = new UInt8(this.value.add(1).value); + xPlus1.assertLessThanOrEqual(y, message); + } + + /** + * Assert that this {@link UInt8} is less than or equal to another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + 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; + } + try { + // x <= y <=> y - x >= 0 which is implied by y - x in [0, 2^16) + let yMinusX = y_.value.sub(this.value).seal(); + Gadgets.rangeCheck16(yMinusX); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Check if this {@link UInt8} is greater than another {@link UInt8}. + * Returns a {@link Bool}. + * + * @example + * ```ts + * // 5 > 3 + * UInt8.from(5).greaterThan(3); + * ``` + */ + greaterThan(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThan(this); + } + + /** + * Check if this {@link UInt8} is greater than or equal another {@link UInt8} value. + * Returns a {@link Bool}. + * + * @example + * ```ts + * // 3 >= 3 + * UInt8.from(3).greaterThanOrEqual(3); + * ``` + */ + greaterThanOrEqual(y: UInt8 | bigint | number) { + return UInt8.from(y).lessThanOrEqual(this); + } + + /** + * Assert that this {@link UInt8} is greater than another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThan(y: UInt8 | bigint | number, message?: string) { + UInt8.from(y).assertLessThan(this, message); + } + + /** + * Assert that this {@link UInt8} is greater than or equal to another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThanOrEqual(y: UInt8, message?: string) { + UInt8.from(y).assertLessThanOrEqual(this, message); + } + + /** + * Assert that this {@link UInt8} is equal another {@link UInt8} value. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param y - the {@link UInt8} value to compare & assert with this {@link UInt8}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertEquals(y: UInt8 | bigint | number, message?: string) { + let y_ = UInt8.from(y); + this.value.assertEquals(y_.value, message); + } + + /** + * Serialize the {@link UInt8} to a string, e.g. for printing. + * + * **Warning**: This operation is not provable. + */ + toString() { + return this.value.toString(); + } + + /** + * Serialize the {@link UInt8} to a number. + * + * **Warning**: This operation is not provable. + */ + toNumber() { + return Number(this.value.toBigInt()); + } + + /** + * Serialize the {@link UInt8} to a bigint. + * + * **Warning**: This operation is not provable. + */ + toBigInt() { + return this.value.toBigInt(); + } + + /** + * {@link Provable.check} for {@link UInt8}. + * Proves that the input is in the [0, 255] range. + */ + static check(x: { value: Field } | Field) { + if (x instanceof Field) x = { value: x }; + Gadgets.rangeCheck8(x.value); + } + + static toInput(x: { value: Field }): HashInput { + return { packed: [[x.value, 8]] }; + } + + /** + * Turns a {@link UInt8} into a {@link UInt32}. + */ + toUInt32(): UInt32 { + return new UInt32(this.value); + } + + /** + * Turns a {@link UInt8} into a {@link UInt64}. + */ + toUInt64(): UInt64 { + return new UInt64(this.value); + } + + /** + * Creates a {@link UInt8} with a value of 255. + */ + static MAXINT() { + return new UInt8((1n << BigInt(UInt8.NUM_BITS)) - 1n); + } + + /** + * Creates a new {@link UInt8}. + */ + static from(x: UInt8 | UInt64 | UInt32 | Field | number | bigint) { + if (x instanceof UInt8) return x; + if (x instanceof UInt64 || x instanceof UInt32 || x instanceof Field) { + // if the input could be larger than 8 bits, we have to prove that it is not + let xx = x instanceof Field ? { value: x } : x; + UInt8.check(xx); + return new UInt8(xx.value.value); + } + return new UInt8(x); + } + + private static checkConstant(x: Field) { + if (!x.isConstant()) return; + Gadgets.rangeCheck8(x); + } +} diff --git a/src/lib/keccak.ts b/src/lib/keccak.ts new file mode 100644 index 0000000000..ac63ceecb1 --- /dev/null +++ b/src/lib/keccak.ts @@ -0,0 +1,550 @@ +import { Field } from './field.js'; +import { Gadgets } from './gadgets/gadgets.js'; +import { assert } from './errors.js'; +import { Provable } from './provable.js'; +import { chunk } from './util/arrays.js'; +import { FlexibleBytes } from './provable-types/bytes.js'; +import { UInt8 } from './int.js'; +import { Bytes } from './provable-types/provable-types.js'; + +export { Keccak }; + +const Keccak = { + /** + * Implementation of [NIST SHA-3](https://csrc.nist.gov/pubs/fips/202/final) Hash Function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Applies the SHA-3 hash function to a list of big-endian byte-sized {@link Field} elements, flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest256 = Keccak.nistSha3(256, preimage); + * let digest384 = Keccak.nistSha3(384, preimage); + * let digest512 = Keccak.nistSha3(512, preimage); + * ``` + * + */ + nistSha3(len: 256 | 384 | 512, message: FlexibleBytes) { + return nistSha3(len, Bytes.from(message)); + }, + /** + * Ethereum-Compatible Keccak-256 Hash Function. + * This is a specialized variant of {@link Keccak.preNist} configured for a 256-bit output length. + * + * Primarily used in Ethereum for hashing transactions, messages, and other types of payloads. + * + * The function accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} of length 32. Both input and output bytes are big-endian. + * + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest = Keccak.ethereum(preimage); + * ``` + */ + ethereum(message: FlexibleBytes) { + return ethereum(Bytes.from(message)); + }, + /** + * Implementation of [pre-NIST Keccak](https://keccak.team/keccak.html) hash function. + * Supports output lengths of 256, 384, or 512 bits. + * + * Keccak won the SHA-3 competition and was slightly altered before being standardized as SHA-3 by NIST in 2015. + * This variant was used in Ethereum before the NIST standardization, by specifying `len` as 256 bits you can obtain the same hash function as used by Ethereum {@link Keccak.ethereum}. + * + * The function applies the pre-NIST Keccak hash function to a list of byte-sized {@link Field} elements and is flexible to handle varying output lengths (256, 384, 512 bits) as specified. + * + * {@link Keccak.preNist} accepts {@link Bytes} as the input message, which is a type that represents a static-length list of byte-sized field elements (range-checked using {@link Gadgets.rangeCheck8}). + * Alternatively, you can pass plain `number[]` of `Uint8Array` to perform a hash outside provable code. + * + * Produces an output of {@link Bytes} that conforms to the chosen bit length. + * Both input and output bytes are big-endian. + * + * @param len - Desired output length in bits. Valid options: 256, 384, 512. + * @param message - Big-endian {@link Bytes} representing the message to hash. + * + * ```ts + * let preimage = Bytes.fromString("hello world"); + * let digest256 = Keccak.preNist(256, preimage); + * let digest384 = Keccak.preNist(384, preimage); + * let digest512= Keccak.preNist(512, preimage); + * ``` + * + */ + preNist(len: 256 | 384 | 512, message: FlexibleBytes) { + return preNist(len, Bytes.from(message)); + }, +}; + +// KECCAK CONSTANTS + +// Length of the square matrix side of Keccak states +const KECCAK_DIM = 5; + +// Value `l` in Keccak, ranges from 0 to 6 and determines the lane width +const KECCAK_ELL = 6; + +// Width of a lane of the state, meaning the length of each word in bits (64) +const KECCAK_WORD = 2 ** KECCAK_ELL; + +// Number of bytes that fit in a word (8) +const BYTES_PER_WORD = KECCAK_WORD / 8; + +// Length of the state in words, 5x5 = 25 +const KECCAK_STATE_LENGTH_WORDS = KECCAK_DIM ** 2; + +// Length of the state in bits, meaning the 5x5 matrix of words in bits (1600) +const KECCAK_STATE_LENGTH = KECCAK_STATE_LENGTH_WORDS * KECCAK_WORD; + +// Length of the state in bytes, meaning the 5x5 matrix of words in bytes (200) +const KECCAK_STATE_LENGTH_BYTES = KECCAK_STATE_LENGTH / 8; + +// Creates the 5x5 table of rotation offset for Keccak modulo 64 +// | i \ j | 0 | 1 | 2 | 3 | 4 | +// | ----- | -- | -- | -- | -- | -- | +// | 0 | 0 | 36 | 3 | 41 | 18 | +// | 1 | 1 | 44 | 10 | 45 | 2 | +// | 2 | 62 | 6 | 43 | 15 | 61 | +// | 3 | 28 | 55 | 25 | 21 | 56 | +// | 4 | 27 | 20 | 39 | 8 | 14 | +const ROT_TABLE = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], +]; + +// Round constants for Keccak +// From https://keccak.team/files/Keccak-reference-3.0.pdf +const ROUND_CONSTANTS = [ + 0x0000000000000001n, + 0x0000000000008082n, + 0x800000000000808an, + 0x8000000080008000n, + 0x000000000000808bn, + 0x0000000080000001n, + 0x8000000080008081n, + 0x8000000000008009n, + 0x000000000000008an, + 0x0000000000000088n, + 0x0000000080008009n, + 0x000000008000000an, + 0x000000008000808bn, + 0x800000000000008bn, + 0x8000000000008089n, + 0x8000000000008003n, + 0x8000000000008002n, + 0x8000000000000080n, + 0x000000000000800an, + 0x800000008000000an, + 0x8000000080008081n, + 0x8000000000008080n, + 0x0000000080000001n, + 0x8000000080008008n, +]; + +// KECCAK HASH FUNCTION + +// Computes the number of required extra bytes to pad a message of length bytes +function bytesToPad(rate: number, length: number): number { + return rate - (length % rate); +} + +// Pads a message M as: +// M || pad[x](|M|) +// The padded message will start with the message argument followed by the padding rule (below) to fulfill a length that is a multiple of rate (in bytes). +// If nist is true, then the padding rule is 0x06 ..0*..1. +// If nist is false, then the padding rule is 10*1. +function pad(message: UInt8[], rate: number, nist: boolean): UInt8[] { + // Find out desired length of the padding in bytes + // If message is already rate bits, need to pad full rate again + const extraBytes = bytesToPad(rate, message.length); + + // 0x06 0x00 ... 0x00 0x80 or 0x86 + const first = nist ? 0x06n : 0x01n; + const last = 0x80n; + + // Create the padding vector + const pad = Array(extraBytes).fill(UInt8.from(0)); + pad[0] = UInt8.from(first); + pad[extraBytes - 1] = pad[extraBytes - 1].add(last); + + // Return the padded message + return [...message, ...pad]; +} + +// ROUND TRANSFORMATION + +// First algorithm in the compression step of Keccak for 64-bit words. +// C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] +// D[i] = C[i-1] xor ROT(C[i+1], 1) +// E[i,j] = A[i,j] xor D[i] +// In the Keccak reference, it corresponds to the `theta` algorithm. +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. +const theta = (state: Field[][]): Field[][] => { + const stateA = state; + + // XOR the elements of each row together + // for all i in {0..4}: C[i] = A[i,0] xor A[i,1] xor A[i,2] xor A[i,3] xor A[i,4] + const stateC = stateA.map((row) => row.reduce(xor)); + + // for all i in {0..4}: D[i] = C[i-1] xor ROT(C[i+1], 1) + const stateD = Array.from({ length: KECCAK_DIM }, (_, i) => + xor( + stateC[(i + KECCAK_DIM - 1) % KECCAK_DIM], + Gadgets.rotate(stateC[(i + 1) % KECCAK_DIM], 1, 'left') + ) + ); + + // for all i in {0..4} and j in {0..4}: E[i,j] = A[i,j] xor D[i] + const stateE = stateA.map((row, index) => + row.map((elem) => xor(elem, stateD[index])) + ); + + return stateE; +}; + +// Second and third steps in the compression step of Keccak for 64-bit words. +// pi: A[i,j] = ROT(E[i,j], r[i,j]) +// rho: A[i,j] = A'[j, 2i+3j mod KECCAK_DIM] +// piRho: B[j,2i+3j] = ROT(E[i,j], r[i,j]) +// which is equivalent to the `rho` algorithm followed by the `pi` algorithm in the Keccak reference as follows: +// rho: +// A[0,0] = a[0,0] +// | i | = | 1 | +// | j | = | 0 | +// for t = 0 to 23 do +// A[i,j] = ROT(a[i,j], (t+1)(t+2)/2 mod 64))) +// | i | = | 0 1 | | i | +// | | = | | * | | +// | j | = | 2 3 | | j | +// end for +// pi: +// for i = 0 to 4 do +// for j = 0 to 4 do +// | I | = | 0 1 | | i | +// | | = | | * | | +// | J | = | 2 3 | | j | +// A[I,J] = a[i,j] +// end for +// end for +// We use the first index of the state array as the i coordinate and the second index as the j coordinate. +function piRho(state: Field[][]): Field[][] { + const stateE = state; + const stateB = State.zeros(); + + // for all i in {0..4} and j in {0..4}: B[j,2i+3j] = ROT(E[i,j], r[i,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateB[j][(2 * i + 3 * j) % KECCAK_DIM] = Gadgets.rotate( + stateE[i][j], + ROT_TABLE[i][j], + 'left' + ); + } + } + + return stateB; +} + +// Fourth step of the compression function of Keccak for 64-bit words. +// F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) +// It corresponds to the chi algorithm in the Keccak reference. +// for j = 0 to 4 do +// for i = 0 to 4 do +// A[i,j] = a[i,j] xor ((not a[i+1,j]) and a[i+2,j]) +// end for +// end for +function chi(state: Field[][]): Field[][] { + const stateB = state; + const stateF = State.zeros(); + + // for all i in {0..4} and j in {0..4}: F[i,j] = B[i,j] xor ((not B[i+1,j]) and B[i+2,j]) + for (let i = 0; i < KECCAK_DIM; i++) { + for (let j = 0; j < KECCAK_DIM; j++) { + stateF[i][j] = xor( + stateB[i][j], + Gadgets.and( + // We can use unchecked NOT because the length of the input is constrained to be 64 bits thanks to the fact that it is the output of a previous Xor64 + Gadgets.not(stateB[(i + 1) % KECCAK_DIM][j], KECCAK_WORD, false), + stateB[(i + 2) % KECCAK_DIM][j], + KECCAK_WORD + ) + ); + } + } + + return stateF; +} + +// Fifth step of the permutation function of Keccak for 64-bit words. +// It takes the word located at the position (0,0) of the state and XORs it with the round constant. +function iota(state: Field[][], rc: bigint): Field[][] { + const stateG = state; + + stateG[0][0] = xor(stateG[0][0], Field.from(rc)); + + return stateG; +} + +// One round of the Keccak permutation function. +// iota o chi o pi o rho o theta +function round(state: Field[][], rc: bigint): Field[][] { + const stateA = state; + const stateE = theta(stateA); + const stateB = piRho(stateE); + const stateF = chi(stateB); + const stateD = iota(stateF, rc); + return stateD; +} + +// Keccak permutation function with a constant number of rounds +function permutation(state: Field[][], rcs: bigint[]): Field[][] { + return rcs.reduce((state, rc) => round(state, rc), state); +} + +// KECCAK SPONGE + +// Absorb padded message into a keccak state with given rate and capacity +function absorb( + paddedMessage: Field[], + capacity: number, + rate: number, + rc: bigint[] +): State { + assert( + rate + capacity === KECCAK_STATE_LENGTH_WORDS, + `invalid rate or capacity (rate + capacity should be ${KECCAK_STATE_LENGTH_WORDS})` + ); + assert( + paddedMessage.length % rate === 0, + 'invalid padded message length (should be multiple of rate)' + ); + + let state = State.zeros(); + + // array of capacity zero words + const zeros = Array(capacity).fill(Field.from(0)); + + for (let idx = 0; idx < paddedMessage.length; idx += rate) { + // split into blocks of rate words + const block = paddedMessage.slice(idx, idx + rate); + // pad the block with 0s to up to KECCAK_STATE_LENGTH_WORDS words + const paddedBlock = block.concat(zeros); + // convert the padded block to a Keccak state + const blockState = State.fromWords(paddedBlock); + // xor the state with the padded block + const stateXor = State.xor(state, blockState); + // apply the permutation function to the xored state + state = permutation(stateXor, rc); + } + return state; +} + +// Squeeze state until it has a desired length in words +function squeeze(state: State, length: number, rate: number): Field[] { + // number of squeezes + const squeezes = Math.floor(length / rate) + 1; + assert(squeezes === 1, 'squeezes should be 1'); + + // Obtain the hash selecting the first `length` words of the output array + const words = State.toWords(state); + const hashed = words.slice(0, length); + return hashed; +} + +// Keccak sponge function for 200 bytes of state width +function sponge( + paddedMessage: Field[], + length: number, + capacity: number, + rate: number +): Field[] { + // check that the padded message is a multiple of rate + assert(paddedMessage.length % rate === 0, 'Invalid padded message length'); + + // absorb + const state = absorb(paddedMessage, capacity, rate, ROUND_CONSTANTS); + + // squeeze + const hashed = squeeze(state, length, rate); + return hashed; +} + +// Keccak hash function with input message passed as list of Field bytes. +// The message will be parsed as follows: +// - the first byte of the message will be the least significant byte of the first word of the state (A[0][0]) +// - the 10*1 pad will take place after the message, until reaching the bit length rate. +// - then, {0} pad will take place to finish the 200 bytes of the state. +function hash( + message: Bytes, + length: number, + capacity: number, + nistVersion: boolean +): UInt8[] { + // Throw errors if used improperly + assert(capacity > 0, 'capacity must be positive'); + assert( + capacity < KECCAK_STATE_LENGTH_BYTES, + `capacity must be less than ${KECCAK_STATE_LENGTH_BYTES}` + ); + assert(length > 0, 'length must be positive'); + + // convert capacity and length to word units + assert(capacity % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + capacity /= BYTES_PER_WORD; + assert(length % BYTES_PER_WORD === 0, 'length must be a multiple of 8'); + length /= BYTES_PER_WORD; + + const rate = KECCAK_STATE_LENGTH_WORDS - capacity; + + // apply padding, convert to words, and hash + const paddedBytes = pad(message.bytes, rate * BYTES_PER_WORD, nistVersion); + const padded = bytesToWords(paddedBytes); + + const hash = sponge(padded, length, capacity, rate); + const hashBytes = wordsToBytes(hash); + + return hashBytes; +} + +// Gadget for NIST SHA-3 function for output lengths 256/384/512. +function nistSha3(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, true); + return BytesOfBitlength[len].from(bytes); +} + +// Gadget for pre-NIST SHA-3 function for output lengths 256/384/512. +// Note that when calling with output length 256 this is equivalent to the ethereum function +function preNist(len: 256 | 384 | 512, message: Bytes): Bytes { + let bytes = hash(message, len / 8, len / 4, false); + return BytesOfBitlength[len].from(bytes); +} + +// Gadget for Keccak hash function for the parameters used in Ethereum. +function ethereum(message: Bytes): Bytes { + return preNist(256, message); +} + +// FUNCTIONS ON KECCAK STATE + +type State = Field[][]; +const State = { + /** + * Create a state of all zeros + */ + zeros(): State { + return Array.from(Array(KECCAK_DIM), (_) => + Array(KECCAK_DIM).fill(Field.from(0)) + ); + }, + + /** + * Flatten state to words + */ + toWords(state: State): Field[] { + const words = Array(KECCAK_STATE_LENGTH_WORDS); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + words[KECCAK_DIM * j + i] = state[i][j]; + } + } + return words; + }, + + /** + * Compose words to state + */ + fromWords(words: Field[]): State { + const state = State.zeros(); + for (let j = 0; j < KECCAK_DIM; j++) { + for (let i = 0; i < KECCAK_DIM; i++) { + state[i][j] = words[KECCAK_DIM * j + i]; + } + } + return state; + }, + + /** + * XOR two states together and return the result + */ + xor(a: State, b: State): State { + assert( + a.length === KECCAK_DIM && a[0].length === KECCAK_DIM, + `invalid \`a\` dimensions (should be ${KECCAK_DIM})` + ); + assert( + b.length === KECCAK_DIM && b[0].length === KECCAK_DIM, + `invalid \`b\` dimensions (should be ${KECCAK_DIM})` + ); + + // Calls xor() on each pair (i,j) of the states input1 and input2 and outputs the output Fields as a new matrix + return a.map((row, i) => row.map((x, j) => xor(x, b[i][j]))); + }, +}; + +// AUXILIARY TYPES + +class Bytes32 extends Bytes(32) {} +class Bytes48 extends Bytes(48) {} +class Bytes64 extends Bytes(64) {} + +const BytesOfBitlength = { + 256: Bytes32, + 384: Bytes48, + 512: Bytes64, +}; + +// AUXILARY FUNCTIONS + +// Auxiliary functions to check the composition of 8 byte values (LE) into a 64-bit word and create constraints for it + +function bytesToWord(wordBytes: UInt8[]): Field { + return wordBytes.reduce((acc, byte, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(byte.value.mul(shift)); + }, Field.from(0)); +} + +function wordToBytes(word: Field): UInt8[] { + let bytes = Provable.witness(Provable.Array(UInt8, BYTES_PER_WORD), () => { + let w = word.toBigInt(); + return Array.from({ length: BYTES_PER_WORD }, (_, k) => + UInt8.from((w >> BigInt(8 * k)) & 0xffn) + ); + }); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +function bytesToWords(bytes: UInt8[]): Field[] { + return chunk(bytes, BYTES_PER_WORD).map(bytesToWord); +} + +function wordsToBytes(words: Field[]): UInt8[] { + return words.flatMap(wordToBytes); +} + +// xor which avoids doing anything on 0 inputs +// (but doesn't range-check the other input in that case) +function xor(x: Field, y: Field): Field { + if (x.isConstant() && x.toBigInt() === 0n) return y; + if (y.isConstant() && y.toBigInt() === 0n) return x; + return Gadgets.xor(x, y, 64); +} diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts new file mode 100644 index 0000000000..2e4c160b3f --- /dev/null +++ b/src/lib/keccak.unit-test.ts @@ -0,0 +1,267 @@ +import { Keccak } from './keccak.js'; +import { ZkProgram } from './proof_system.js'; +import { equivalent, equivalentAsync } from './testing/equivalent.js'; +import { + keccak_224, + keccak_256, + keccak_384, + keccak_512, + sha3_224, + sha3_256, + sha3_384, + sha3_512, +} from '@noble/hashes/sha3'; +import { Bytes } from './provable-types/provable-types.js'; +import { bytes } from './gadgets/test-utils.js'; +import { UInt8 } from './int.js'; +import { test, Random, sample } from './testing/property.js'; +import { expect } from 'expect'; + +const RUNS = 1; + +const testImplementations = { + sha3: { + 224: sha3_224, + 256: sha3_256, + 384: sha3_384, + 512: sha3_512, + }, + preNist: { + 224: keccak_224, + 256: keccak_256, + 384: keccak_384, + 512: keccak_512, + }, +}; + +const lengths = [256, 384, 512] as const; + +// EQUIVALENCE TESTS AGAINST REF IMPLEMENTATION + +// checks outside circuit +// TODO: fix witness generation slowness + +for (let length of lengths) { + let [preimageLength] = sample(Random.nat(100), 1); + console.log(`Testing ${length} with preimage length ${preimageLength}`); + let inputBytes = bytes(preimageLength); + let outputBytes = bytes(length / 8); + + equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.sha3[length], + (x) => Keccak.nistSha3(length, x), + `sha3 ${length}` + ); + + equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + testImplementations.preNist[length], + (x) => Keccak.preNist(length, x), + `keccak ${length}` + ); + + // bytes to hex roundtrip + equivalent({ from: [inputBytes], to: inputBytes })( + (x) => x, + (x) => Bytes.fromHex(x.toHex()), + `Bytes toHex` + ); +} + +// EQUIVALENCE TESTS AGAINST TEST VECTORS (at the bottom) + +for (let { nist, length, message, expected } of testVectors()) { + let Hash = nist ? Keccak.nistSha3 : Keccak.preNist; + let actual = Hash(length, Bytes.fromHex(message)); + expect(actual).toEqual(Bytes.fromHex(expected)); +} + +// MISC QUICK TESTS + +// Test constructor +test(Random.uint8, Random.uint8, (x, y, assert) => { + let z = new UInt8(x); + assert(z instanceof UInt8); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + + assert((z = new UInt8(x)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z)) instanceof UInt8 && z.toBigInt() === x); + assert((z = new UInt8(z.value.value)) instanceof UInt8 && z.toBigInt() === x); + + z = new UInt8(y); + assert(z instanceof UInt8); + assert(z.toString() === y.toString()); +}); + +// handles all numbers up to 2^8 +test(Random.nat(255), (n, assert) => { + assert(UInt8.from(n).toString() === String(n)); +}); + +// throws on negative numbers +test.negative(Random.int(-10, -1), (x) => UInt8.from(x)); + +// throws on numbers >= 2^8 +test.negative(Random.uint8.invalid, (x) => UInt8.from(x)); + +// PROOF TESTS + +// Choose a test length at random +const digestLength = lengths[Math.floor(Math.random() * 3)]; + +// Digest length in bytes +const digestLengthBytes = digestLength / 8; + +const preImageLength = 32; + +// No need to test Ethereum because it's just a special case of preNist +const KeccakProgram = ZkProgram({ + name: `keccak-test-${digestLength}`, + publicInput: Bytes(preImageLength).provable, + publicOutput: Bytes(digestLengthBytes).provable, + methods: { + nistSha3: { + privateInputs: [], + method(preImage: Bytes) { + return Keccak.nistSha3(digestLength, preImage); + }, + }, + preNist: { + privateInputs: [], + method(preImage: Bytes) { + return Keccak.preNist(digestLength, preImage); + }, + }, + }, +}); + +await KeccakProgram.compile(); + +// SHA-3 +await equivalentAsync( + { + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), + }, + { runs: RUNS } +)(testImplementations.sha3[digestLength], async (x) => { + const proof = await KeccakProgram.nistSha3(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); + +// PreNIST Keccak +await equivalentAsync( + { + from: [bytes(preImageLength)], + to: bytes(digestLengthBytes), + }, + { runs: RUNS } +)(testImplementations.preNist[digestLength], async (x) => { + const proof = await KeccakProgram.preNist(x); + await KeccakProgram.verify(proof); + return proof.publicOutput; +}); + +// TEST VECTORS + +function testVectors(): { + nist: boolean; + length: 256 | 384 | 512; + message: string; + expected: string; +}[] { + return [ + { + nist: false, + length: 256, + message: '30', + expected: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d', + }, + { + nist: true, + length: 512, + message: '30', + expected: + '2d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c', + }, + { + nist: false, + length: 256, + message: + '4920616d20746865206f776e6572206f6620746865204e465420776974682069642058206f6e2074686520457468657265756d20636861696e', + expected: + '63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + }, + { + nist: false, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '560deb1d387f72dba729f0bd0231ad45998dda4b53951645322cf95c7b6261d9', + }, + { + nist: true, + length: 256, + message: + '044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116df9e2eaaa42d9fe9e558a9b8ef1bf366f190aacaa83bad2641ee106e9041096e42d44da53f305ab94b6365837b9803627ab098c41a6013694f9b468bccb9c13e95b3900365eb58924de7158a54467e984efcfdabdbcc9af9a940d49c51455b04c63858e0487687c3eeb30796a3e9307680e1b81b860b01c88ff74545c2c314e36', + expected: + '1784354c4bbfa5f54e5db23041089e65a807a7b970e3cfdba95e2fbe63b1c0e4', + }, + { + nist: false, + length: 256, + message: + '391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '7d5655391ede9ca2945f32ad9696f464be8004389151ce444c89f688278f2e1d', + }, + { + nist: false, + length: 256, + message: + 'ff391ccf9b5de23bb86ec6b2b142adb6e9ba6bee8519e7502fb8be8959fbd2672934cc3e13b7b45bf2b8a5cb48881790a7438b4a326a0c762e31280711e6b64fcc2e3e4e631e501d398861172ea98603618b8f23b91d0208b0b992dfe7fdb298b6465adafbd45e4f88ee9dc94e06bc4232be91587f78572c169d4de4d8b95b714ea62f1fbf3c67a4', + expected: + '37694fd4ba137be747eb25a85b259af5563e0a7a3010d42bd15963ac631b9d3f', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: + '80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + expected: + 'bbf1f49a2cc5678aa62196d0c3108d89425b81780e1e90bcec03b4fb5f834714', + }, + { + nist: false, + length: 256, + message: 'a2c0', + expected: + '9856642c690c036527b8274db1b6f58c0429a88d9f3b9298597645991f4f58f0', + }, + { + nist: false, + length: 256, + message: '0a2c', + expected: + '295b48ad49eff61c3abfd399c672232434d89a4ef3ca763b9dbebb60dbb32a8b', + }, + { + nist: false, + length: 256, + message: '00', + expected: + 'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a', + }, + ]; +} diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 28d6e3e7bf..e3b5823a9c 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -482,7 +482,7 @@ function LocalBlockchain({ account, update, commitments, - proofsEnabled + this.proofsEnabled ); } } @@ -587,7 +587,7 @@ function LocalBlockchain({ // and hopefully with upcoming work by Matt we can just run everything in the prover, and nowhere else let tx = createTransaction(sender, f, 0, { isFinalRunOutsideCircuit: false, - proofsEnabled, + proofsEnabled: this.proofsEnabled, fetchMode: 'test', }); let hasProofs = tx.transaction.accountUpdates.some( @@ -595,7 +595,7 @@ function LocalBlockchain({ ); return createTransaction(sender, f, 1, { isFinalRunOutsideCircuit: !hasProofs, - proofsEnabled, + proofsEnabled: this.proofsEnabled, }); }, applyJsonTransaction(json: string) { @@ -666,7 +666,7 @@ function LocalBlockchain({ networkState.totalCurrency = currency; }, setProofsEnabled(newProofsEnabled: boolean) { - proofsEnabled = newProofsEnabled; + this.proofsEnabled = newProofsEnabled; }, }; } @@ -1240,7 +1240,7 @@ function getProofsEnabled() { } function dummyAccount(pubkey?: PublicKey): Account { - let dummy = Types.Account.emptyValue(); + let dummy = Types.Account.empty(); if (pubkey) dummy.publicKey = pubkey; return dummy; } diff --git a/src/lib/mina/account.ts b/src/lib/mina/account.ts index c45d38587b..dc31d0d864 100644 --- a/src/lib/mina/account.ts +++ b/src/lib/mina/account.ts @@ -10,6 +10,7 @@ import { TypeMap, } from '../../bindings/mina-transaction/gen/transaction.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; +import { ProvableExtended } from '../circuit_value.js'; export { FetchedAccount, Account, PartialAccount }; export { accountQuery, parseFetchedAccount, fillPartialAccount }; @@ -184,19 +185,14 @@ function parseFetchedAccount({ } function fillPartialAccount(account: PartialAccount): Account { - return genericLayoutFold( + return genericLayoutFold>( TypeMap, customTypes, { map(type, value) { // if value exists, use it; otherwise fall back to dummy value if (value !== undefined) return value; - // fall back to dummy value - if (type.emptyValue) return type.emptyValue(); - return type.fromFields( - Array(type.sizeInFields()).fill(Field(0)), - type.toAuxiliary() - ); + return type.empty(); }, reduceArray(array) { return array; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 1d3ace221a..59ebf57ec4 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -190,7 +190,7 @@ class Proof { async function verify( proof: Proof | JsonProof, - verificationKey: string + verificationKey: string | VerificationKey ) { let picklesProof: Pickles.Proof; let statement: Pickles.Statement; @@ -215,10 +215,12 @@ async function verify( let output = toFieldConsts(type.output, proof.publicOutput); statement = MlPair(input, output); } + let vk = + typeof verificationKey === 'string' + ? verificationKey + : verificationKey.data; return prettifyStacktracePromise( - withThreadPool(() => - Pickles.verify(statement, picklesProof, verificationKey) - ) + withThreadPool(() => Pickles.verify(statement, picklesProof, vk)) ); } diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index 54b95e6d45..3aab1dc9a5 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -1,20 +1,124 @@ -import { Field } from './core.js'; +import { Field, Bool } from './core.js'; import { Struct } from './circuit_value.js'; import { UInt64 } from './int.js'; -import { ZkProgram } from './proof_system.js'; +import { + CompiledTag, + Empty, + Proof, + ZkProgram, + picklesRuleFromFunction, + sortMethodArguments, +} from './proof_system.js'; import { expect } from 'expect'; +import { Pickles, ProvablePure, Snarky } from '../snarky.js'; +import { AnyFunction } from './util/types.js'; +import { snarkContext } from './provable-context.js'; +import { it } from 'node:test'; +import { Provable } from './provable.js'; +import { bool, equivalentAsync, field, record } from './testing/equivalent.js'; +import { FieldConst, FieldVar } from './field.js'; const EmptyProgram = ZkProgram({ name: 'empty', publicInput: Field, + methods: { run: { privateInputs: [], method: (_) => {} } }, +}); + +class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} + +// unit-test zkprogram creation helpers: +// -) sortMethodArguments +// -) picklesRuleFromFunction + +it('pickles rule creation', async () => { + // a rule that verifies a proof conditionally, and returns the proof's input as output + function main(proof: EmptyProof, shouldVerify: Bool) { + proof.verifyIf(shouldVerify); + return proof.publicInput; + } + let privateInputs = [EmptyProof, Bool]; + + // collect method interface + let methodIntf = sortMethodArguments('mock', 'main', privateInputs, Proof); + + expect(methodIntf).toEqual({ + methodName: 'main', + witnessArgs: [Bool], + proofArgs: [EmptyProof], + allArgs: [ + { type: 'proof', index: 0 }, + { type: 'witness', index: 0 }, + ], + genericArgs: [], + }); + + // store compiled tag + CompiledTag.store(EmptyProgram, 'mock tag'); + + // create pickles rule + let rule: Pickles.Rule = picklesRuleFromFunction( + Empty as ProvablePure, + Field as ProvablePure, + main as AnyFunction, + { name: 'mock' }, + methodIntf, + [] + ); + + await equivalentAsync( + { from: [field, bool], to: record({ field, bool }) }, + { runs: 5 } + )( + (field, bool) => ({ field, bool }), + async (field, bool) => { + let dummy = await EmptyProof.dummy(field, undefined, 0); + let field_: FieldConst = [0, 0n]; + let bool_: FieldConst = [0, 0n]; + + Provable.runAndCheck(() => { + // put witnesses in snark context + snarkContext.get().witnesses = [dummy, bool]; + + // call pickles rule + let { + publicOutput: [, publicOutput], + shouldVerify: [, shouldVerify], + } = rule.main([0]); + + // `publicOutput` and `shouldVerify` are as expected + Snarky.field.assertEqual(publicOutput, dummy.publicInput.value); + Snarky.field.assertEqual(shouldVerify, bool.value); + + Provable.asProver(() => { + field_ = Snarky.field.readVar(publicOutput); + bool_ = Snarky.field.readVar(shouldVerify); + }); + }); + + return { field: Field(field_), bool: Bool(FieldVar.constant(bool_)) }; + } + ); +}); + +// compile works with large inputs + +const N = 100_000; + +const program = ZkProgram({ + name: 'large-array-program', methods: { - run: { - privateInputs: [], - method: (publicInput: Field) => {}, + baseCase: { + privateInputs: [Provable.Array(Field, N)], + method(_: Field[]) {}, }, }, }); +it('can compile program with large input', async () => { + await program.compile(); +}); + +// regression tests for some zkprograms const emptyMethodsMetadata = EmptyProgram.analyzeMethods(); expect(emptyMethodsMetadata.run).toEqual( expect.objectContaining({ diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 0ce0030556..3b79562889 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -1,5 +1,5 @@ import { Context } from './global-context.js'; -import { Gate, JsonGate, Snarky } from '../snarky.js'; +import { Gate, GateType, JsonGate, Snarky } from '../snarky.js'; import { parseHexString } from '../bindings/crypto/bigint-helpers.js'; import { prettifyStacktrace } from './errors.js'; import { Fp } from '../bindings/crypto/finite_field.js'; @@ -105,6 +105,15 @@ function constraintSystem(f: () => T) { print() { printGates(gates); }, + summary() { + let gateTypes: Partial> = {}; + gateTypes['Total rows'] = rows; + for (let gate of gates) { + gateTypes[gate.type] ??= 0; + gateTypes[gate.type]!++; + } + return gateTypes; + }, }; } catch (error) { throw prettifyStacktrace(error); diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts new file mode 100644 index 0000000000..992e10bd1f --- /dev/null +++ b/src/lib/provable-types/bytes.ts @@ -0,0 +1,121 @@ +import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; +import { ProvablePureExtended } from '../circuit_value.js'; +import { assert } from '../gadgets/common.js'; +import { chunkString } from '../util/arrays.js'; +import { Provable } from '../provable.js'; +import { UInt8 } from '../int.js'; + +export { Bytes, createBytes, FlexibleBytes }; + +type FlexibleBytes = Bytes | (UInt8 | bigint | number)[] | Uint8Array; + +/** + * A provable type representing an array of bytes. + */ +class Bytes { + bytes: UInt8[]; + + constructor(bytes: UInt8[]) { + let size = (this.constructor as typeof Bytes).size; + + // assert that data is not too long + assert( + bytes.length <= size, + `Expected at most ${size} bytes, got ${bytes.length}` + ); + + // pad the data with zeros + let padding = Array.from( + { length: size - bytes.length }, + () => new UInt8(0) + ); + this.bytes = bytes.concat(padding); + } + + /** + * Coerce the input to {@link Bytes}. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static from(data: (UInt8 | bigint | number)[] | Uint8Array | Bytes): Bytes { + if (data instanceof Bytes) return data; + if (this._size === undefined) { + let Bytes_ = createBytes(data.length); + return Bytes_.from(data); + } + return new this([...data].map(UInt8.from)); + } + + toBytes(): Uint8Array { + return Uint8Array.from(this.bytes.map((x) => x.toNumber())); + } + + toFields() { + return this.bytes.map((x) => x.value); + } + + /** + * Create {@link Bytes} from a string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromString(s: string) { + let bytes = new TextEncoder().encode(s); + return this.from(bytes); + } + + /** + * Create {@link Bytes} from a hex string. + * + * Inputs smaller than `this.size` are padded with zero bytes. + */ + static fromHex(xs: string): Bytes { + let bytes = chunkString(xs, 2).map((s) => parseInt(s, 16)); + return this.from(bytes); + } + + /** + * Convert {@link Bytes} to a hex string. + */ + toHex(): string { + return this.bytes + .map((x) => x.toBigInt().toString(16).padStart(2, '0')) + .join(''); + } + + // dynamic subclassing infra + static _size?: number; + static _provable?: ProvablePureExtended< + Bytes, + { bytes: { value: string }[] } + >; + + /** + * The size of the {@link Bytes}. + */ + static get size() { + assert(this._size !== undefined, 'Bytes not initialized'); + return this._size; + } + + get length() { + return this.bytes.length; + } + + /** + * `Provable` + */ + static get provable() { + assert(this._provable !== undefined, 'Bytes not initialized'); + return this._provable; + } +} + +function createBytes(size: number): typeof Bytes { + return class Bytes_ extends Bytes { + static _size = size; + static _provable = provableFromClass(Bytes_, { + bytes: Provable.Array(UInt8, size), + }); + }; +} diff --git a/src/lib/provable-types/provable-types.ts b/src/lib/provable-types/provable-types.ts new file mode 100644 index 0000000000..ad11e446b0 --- /dev/null +++ b/src/lib/provable-types/provable-types.ts @@ -0,0 +1,21 @@ +import { Bytes as InternalBytes, createBytes } from './bytes.js'; + +export { Bytes }; + +type Bytes = InternalBytes; + +/** + * A provable type representing an array of bytes. + * + * ```ts + * class Bytes32 extends Bytes(32) {} + * + * let bytes = Bytes32.fromHex('deadbeef'); + * ``` + */ +function Bytes(size: number) { + return createBytes(size); +} +Bytes.from = InternalBytes.from; +Bytes.fromHex = InternalBytes.fromHex; +Bytes.fromString = InternalBytes.fromString; diff --git a/src/lib/provable.ts b/src/lib/provable.ts index bd90d66455..8094bcf89e 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -3,7 +3,6 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ -import { FieldVar } from './field.js'; import { Field, Bool } from './core.js'; import { Provable as Provable_, Snarky } from '../snarky.js'; import type { FlexibleProvable, ProvableExtended } from './circuit_value.js'; @@ -14,7 +13,6 @@ import { InferProvable, InferredProvable, } from '../bindings/lib/provable-snarky.js'; -import { isField } from './field.js'; import { inCheckedComputation, inProver, @@ -24,7 +22,6 @@ import { runUnchecked, constraintSystem, } from './provable-context.js'; -import { isBool } from './bool.js'; // external API export { Provable }; @@ -127,7 +124,8 @@ const Provable = { * @example * ```ts * let x = Field(42); - * Provable.isConstant(x); // true + * Provable.isConstant(Field, x); // true + * ``` */ isConstant, /** @@ -345,11 +343,7 @@ function ifImplicit(condition: Bool, x: T, y: T): T { ); // TODO remove second condition once we have consolidated field class back into one // if (type !== y.constructor) { - if ( - type !== y.constructor && - !(isField(x) && isField(y)) && - !(isBool(x) && isBool(y)) - ) { + if (type !== y.constructor) { throw Error( 'Provable.if: Mismatched argument types. Try using an explicit type argument:\n' + `Provable.if(bool, MyType, x, y)` @@ -581,5 +575,12 @@ function provableArray>( HashInput.empty ); }, + + empty() { + if (!('empty' in type)) { + throw Error('circuitArray.empty: element type has no empty() method'); + } + return Array.from({ length }, () => type.empty()); + }, } satisfies ProvableExtended as any; } diff --git a/src/lib/provable.unit-test.ts b/src/lib/provable.unit-test.ts new file mode 100644 index 0000000000..9c14a0f02c --- /dev/null +++ b/src/lib/provable.unit-test.ts @@ -0,0 +1,23 @@ +import { it } from 'node:test'; +import { Provable } from './provable.js'; +import { exists } from './gadgets/common.js'; +import { Field } from './field.js'; +import { expect } from 'expect'; + +it('can witness large field array', () => { + let N = 100_000; + let arr = Array(N).fill(0n); + + Provable.runAndCheck(() => { + // with exists + let fields = exists(N, () => arr); + + // with Provable.witness + let fields2 = Provable.witness(Provable.Array(Field, N), () => + arr.map(Field.from) + ); + + expect(fields.length).toEqual(N); + expect(fields2.length).toEqual(N); + }); +}); diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 176478947f..d54f20c209 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -88,7 +88,7 @@ class Scalar { * Convert this {@link Scalar} into a bigint */ toBigInt() { - return this.#assertConstant('toBigInt'); + return assertConstant(this, 'toBigInt'); } // TODO: fix this API. we should represent "shifted status" internally and use @@ -112,17 +112,13 @@ class Scalar { // operations on constant scalars - #assertConstant(name: string) { - return constantScalarToBigint(this, `Scalar.${name}`); - } - /** * Negate a scalar field element. * * **Warning**: This method is not available for provable code. */ neg() { - let x = this.#assertConstant('neg'); + let x = assertConstant(this, 'neg'); let z = Fq.negate(x); return Scalar.from(z); } @@ -133,8 +129,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ add(y: Scalar) { - let x = this.#assertConstant('add'); - let y0 = y.#assertConstant('add'); + let x = assertConstant(this, 'add'); + let y0 = assertConstant(y, 'add'); let z = Fq.add(x, y0); return Scalar.from(z); } @@ -145,8 +141,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ sub(y: Scalar) { - let x = this.#assertConstant('sub'); - let y0 = y.#assertConstant('sub'); + let x = assertConstant(this, 'sub'); + let y0 = assertConstant(y, 'sub'); let z = Fq.sub(x, y0); return Scalar.from(z); } @@ -157,8 +153,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ mul(y: Scalar) { - let x = this.#assertConstant('mul'); - let y0 = y.#assertConstant('mul'); + let x = assertConstant(this, 'mul'); + let y0 = assertConstant(y, 'mul'); let z = Fq.mul(x, y0); return Scalar.from(z); } @@ -170,8 +166,8 @@ class Scalar { * **Warning**: This method is not available for provable code. */ div(y: Scalar) { - let x = this.#assertConstant('div'); - let y0 = y.#assertConstant('div'); + let x = assertConstant(this, 'div'); + let y0 = assertConstant(y, 'div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.from(z); @@ -179,11 +175,11 @@ class Scalar { // TODO don't leak 'shifting' to the user and remove these methods shift() { - let x = this.#assertConstant('shift'); + let x = assertConstant(this, 'shift'); return Scalar.from(shift(x)); } unshift() { - let x = this.#assertConstant('unshift'); + let x = assertConstant(this, 'unshift'); return Scalar.from(unshift(x)); } @@ -196,7 +192,7 @@ class Scalar { * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. */ toFieldsCompressed(): { field: Field; highBit: Bool } { - let s = this.#assertConstant('toFieldsCompressed'); + let s = assertConstant(this, 'toFieldsCompressed'); let lowBitSize = BigInt(Fq.sizeInBits - 1); let lowBitMask = (1n << lowBitSize) - 1n; return { @@ -292,7 +288,7 @@ class Scalar { * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static toJSON(x: Scalar) { - let s = x.#assertConstant('toJSON'); + let s = assertConstant(x, 'toJSON'); return s.toString(); } @@ -312,6 +308,12 @@ class Scalar { } } +// internal helpers + +function assertConstant(x: Scalar, name: string) { + return constantScalarToBigint(x, `Scalar.${name}`); +} + function toConstantScalar([, ...bits]: MlArray): Fq | undefined { if (bits.length !== Fq.sizeInBits) throw Error( diff --git a/src/lib/signature.ts b/src/lib/signature.ts index e26e68ca2b..58d68ccef0 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -165,8 +165,8 @@ class PublicKey extends CircuitValue { * Creates an empty {@link PublicKey}. * @returns an empty {@link PublicKey} */ - static empty() { - return PublicKey.from({ x: Field(0), isOdd: Bool(false) }); + static empty(): InstanceType { + return PublicKey.from({ x: Field(0), isOdd: Bool(false) }) as any; } /** diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 2922afa11d..1f8ad9ba52 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -389,7 +389,7 @@ function drawFieldVar(): FieldVar { let fieldType = drawFieldType(); switch (fieldType) { case FieldType.Constant: { - return FieldVar.constant(17n); + return FieldVar.constant(1n); } case FieldType.Var: { return [FieldType.Var, 0]; @@ -397,10 +397,14 @@ function drawFieldVar(): FieldVar { case FieldType.Add: { let x = drawFieldVar(); let y = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant && y[0] === FieldType.Constant) return x; return FieldVar.add(x, y); } case FieldType.Scale: { let x = drawFieldVar(); + // prevent blow-up of constant size + if (x[0] === FieldType.Constant) return x; return FieldVar.scale(3n, x); } } diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index c02cf23afc..7a0f20fd7d 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -5,6 +5,9 @@ import { test, Random } from '../testing/property.js'; import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; import { Bool, Field } from '../core.js'; +import { AnyFunction, Tuple } from '../util/types.js'; +import { provable } from '../circuit_value.js'; +import { assert } from '../gadgets/common.js'; export { equivalent, @@ -17,6 +20,7 @@ export { id, }; export { + spec, field, fieldWithRng, bigintField, @@ -26,7 +30,10 @@ export { array, record, map, + onlyIf, fromRandom, + first, + second, }; export { Spec, @@ -115,7 +122,7 @@ function toUnion(spec: OrUnion): FromSpecUnion { function equivalent< In extends Tuple>, Out extends ToSpec ->({ from, to }: { from: In; to: Out }) { +>({ from, to, verbose }: { from: In; to: Out; verbose?: boolean }) { return function run( f1: (...args: Params1) => First, f2: (...args: Params2) => Second, @@ -123,7 +130,8 @@ function equivalent< ) { let generators = from.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; - test(...(generators as any[]), (...args) => { + let start = performance.now(); + let nRuns = test(...(generators as any[]), (...args) => { args.pop(); let inputs = args as Params1; handleErrors( @@ -136,6 +144,14 @@ function equivalent< label ); }); + + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log( + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` + ); + } }; } @@ -180,11 +196,17 @@ function equivalentAsync< // equivalence tester for provable code +function isProvable(spec: FromSpecUnion) { + return spec.specs.some((spec) => spec.provable); +} + function equivalentProvable< In extends Tuple>, Out extends ToSpec ->({ from: fromRaw, to }: { from: In; to: Out }) { +>({ from: fromRaw, to, verbose }: { from: In; to: Out; verbose?: boolean }) { let fromUnions = fromRaw.map(toUnion); + assert(fromUnions.some(isProvable), 'equivalentProvable: no provable input'); + return function run( f1: (...args: Params1) => First, f2: (...args: Params2) => Second, @@ -192,7 +214,9 @@ function equivalentProvable< ) { let generators = fromUnions.map((spec) => spec.rng); let assertEqual = to.assertEqual ?? deepEqual; - test(...generators, (...args) => { + + let start = performance.now(); + let nRuns = test.custom({ minRuns: 5 })(...generators, (...args) => { args.pop(); // figure out which spec to use for each argument @@ -223,10 +247,53 @@ function equivalentProvable< handleErrors( () => f1(...inputs), () => f2(...inputWitnesses), - (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)) + (x, y) => Provable.asProver(() => assertEqual(x, to.back(y), label)), + label ); }); }); + if (verbose) { + let ms = (performance.now() - start).toFixed(1); + let runs = nRuns.toString().padStart(2, ' '); + console.log( + `${label.padEnd(20, ' ')} success on ${runs} runs in ${ms}ms.` + ); + } + }; +} + +// creating specs + +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable: Provable; +}): ProvableSpec; +function spec(spec: { + rng: Random; + there: (x: T) => S; + back: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + assertEqual?: (x: T, y: T, message: string) => void; +}): Spec; +function spec(spec: { + rng: Random; + there?: (x: T) => S; + back?: (x: S) => T; + assertEqual?: (x: T, y: T, message: string) => void; + provable?: Provable; +}): Spec { + return { + rng: spec.rng, + there: spec.there ?? (id as any), + back: spec.back ?? (id as any), + assertEqual: spec.assertEqual, + provable: spec.provable, }; } @@ -290,10 +357,14 @@ function record }>( { [k in keyof Specs]: First }, { [k in keyof Specs]: Second } > { + let isProvable = Object.values(specs).every((spec) => spec.provable); return { rng: Random.record(mapObject(specs, (spec) => spec.rng)) as any, there: (x) => mapObject(specs, (spec, k) => spec.there(x[k])) as any, back: (x) => mapObject(specs, (spec, k) => spec.back(x[k])) as any, + provable: isProvable + ? provable(mapObject(specs, (spec) => spec.provable) as any) + : undefined, }; } @@ -304,6 +375,10 @@ function map( return { ...to, rng: Random.map(from.rng, there) }; } +function onlyIf(spec: Spec, onlyIf: (t: T) => boolean): Spec { + return { ...spec, rng: Random.reject(spec.rng, (x) => !onlyIf(x)) }; +} + function mapObject( t: { [k in K]: T }, map: (t: T, k: K) => S @@ -317,6 +392,18 @@ function fromRandom(rng: Random): Spec { return { rng, there: id, back: id }; } +function first(spec: Spec): Spec { + return { rng: spec.rng, there: id, back: id }; +} +function second(spec: Spec): Spec { + return { + rng: Random.map(spec.rng, spec.there), + there: id, + back: id, + provable: spec.provable, + }; +} + // helper to ensure two functions throw equivalent errors function handleErrors( @@ -383,12 +470,6 @@ function throwError(message?: string): any { throw Error(message); } -// helper types - -type AnyFunction = (...args: any) => any; - -type Tuple = [] | [T, ...T[]]; - // infer input types from specs type Param1> = In extends { diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 0d9c457cf2..8dd41ac38f 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -5,7 +5,7 @@ import { Json, AccountUpdate, ZkappCommand, - emptyValue, + empty, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import { AuthRequired, @@ -26,7 +26,7 @@ import { import { genericLayoutFold } from '../../bindings/lib/from-layout.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { - GenericProvable, + PrimitiveTypeMap, primitiveTypeMap, } from '../../bindings/lib/generic.js'; import { Scalar, PrivateKey, Group } from '../../provable/curve-bigint.js'; @@ -35,7 +35,7 @@ import { randomBytes } from '../../bindings/crypto/random.js'; import { alphabet } from '../base58.js'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { Memo } from '../../mina-signer/src/memo.js'; -import { ProvableExtended } from '../../bindings/lib/provable-bigint.js'; +import { Signable } from '../../bindings/lib/provable-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; @@ -66,6 +66,7 @@ function sample(rng: Random, size: number) { const boolean = Random_(() => drawOneOf8() < 4); const bool = map(boolean, Bool); +const uint8 = biguintWithInvalid(8); const uint32 = biguintWithInvalid(32); const uint64 = biguintWithInvalid(64); const byte = Random_(drawRandomByte); @@ -81,7 +82,7 @@ const keypair = map(privateKey, (privatekey) => ({ publicKey: PrivateKey.toPublicKey(privatekey), })); -const tokenId = oneOf(TokenId.emptyValue(), field); +const tokenId = oneOf(TokenId.empty(), field); const stateHash = field; const authRequired = map( oneOf( @@ -106,16 +107,16 @@ const actions = mapWithInvalid( array(array(field, int(1, 5)), nat(2)), Actions.fromList ); -const actionState = oneOf(ActionState.emptyValue(), field); -const verificationKeyHash = oneOf(VerificationKeyHash.emptyValue(), field); -const receiptChainHash = oneOf(ReceiptChainHash.emptyValue(), field); +const actionState = oneOf(ActionState.empty(), field); +const verificationKeyHash = oneOf(VerificationKeyHash.empty(), field); +const receiptChainHash = oneOf(ReceiptChainHash.empty(), field); const zkappUri = map(string(nat(50)), ZkappUri.fromJSON); -const PrimitiveMap = primitiveTypeMap(); -type Types = typeof TypeMap & typeof customTypes & typeof PrimitiveMap; -type Provable = GenericProvable; +type Types = typeof TypeMap & typeof customTypes & PrimitiveTypeMap; type Generators = { - [K in keyof Types]: Types[K] extends Provable ? Random : never; + [K in keyof Types]: Types[K] extends Signable + ? Random + : never; }; const Generators: Generators = { Field: field, @@ -138,8 +139,8 @@ const Generators: Generators = { string: base58(nat(50)), // TODO replace various strings, like signature, with parsed types number: nat(3), }; -let typeToBigintGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToBigintGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, Generators[key as keyof Generators]]) @@ -187,17 +188,20 @@ const nonNumericString = reject( string(nat(20)), (str: any) => !isNaN(str) && !isNaN(parseFloat(str)) ); -const invalidUint64Json = toString( - oneOf(uint64.invalid, nonInteger, nonNumericString) +const invalidUint8Json = toString( + oneOf(uint8.invalid, nonInteger, nonNumericString) ); const invalidUint32Json = toString( oneOf(uint32.invalid, nonInteger, nonNumericString) ); +const invalidUint64Json = toString( + oneOf(uint64.invalid, nonInteger, nonNumericString) +); // some json versions of those types let json_ = { - uint64: { ...toString(uint64), invalid: invalidUint64Json }, uint32: { ...toString(uint32), invalid: invalidUint32Json }, + uint64: { ...toString(uint64), invalid: invalidUint64Json }, publicKey: withInvalidBase58(mapWithInvalid(publicKey, PublicKey.toBase58)), privateKey: withInvalidBase58(map(privateKey, PrivateKey.toBase58)), keypair: map(keypair, ({ privatekey, publicKey }) => ({ @@ -214,7 +218,7 @@ function withInvalidRandomString(rng: Random) { } type JsonGenerators = { - [K in keyof Types]: Types[K] extends ProvableExtended + [K in keyof Types]: Types[K] extends Signable ? Random : never; }; @@ -241,8 +245,8 @@ const JsonGenerators: JsonGenerators = { string: base58(nat(50)), number: nat(3), }; -let typeToJsonGenerator = new Map, Random>( - [TypeMap, PrimitiveMap, customTypes] +let typeToJsonGenerator = new Map, Random>( + [TypeMap, primitiveTypeMap, customTypes] .map(Object.entries) .flat() .map(([key, value]) => [value, JsonGenerators[key as keyof JsonGenerators]]) @@ -310,6 +314,7 @@ const Random = Object.assign(Random_, { field, otherField: fieldWithInvalid, bool, + uint8, uint32, uint64, biguint: biguintWithInvalid, @@ -329,7 +334,13 @@ function generatorFromLayout( { isJson }: { isJson: boolean } ): Random { let typeToGenerator = isJson ? typeToJsonGenerator : typeToBigintGenerator; - return genericLayoutFold, TypeMap, Json.TypeMap>( + return genericLayoutFold< + Signable, + undefined, + Random, + TypeMap, + Json.TypeMap + >( TypeMap, customTypes, { @@ -359,7 +370,7 @@ function generatorFromLayout( } else { return mapWithInvalid(isSome, value, (isSome, value) => { let isSomeBoolean = TypeMap.Bool.toJSON(isSome); - if (!isSomeBoolean) return emptyValue(typeData); + if (!isSomeBoolean) return empty(typeData); return { isSome, value }; }); } diff --git a/src/lib/testing/testing.unit-test.ts b/src/lib/testing/testing.unit-test.ts index 4a0abe91fb..6041b155b4 100644 --- a/src/lib/testing/testing.unit-test.ts +++ b/src/lib/testing/testing.unit-test.ts @@ -6,11 +6,11 @@ import { PublicKey, UInt32, UInt64, - provableFromLayout, + signableFromLayout, ZkappCommand, Json, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { test, Random, sample } from './property.js'; +import { test, Random } from './property.js'; // some trivial roundtrip tests test(Random.accountUpdate, (accountUpdate, assert) => { @@ -20,10 +20,11 @@ test(Random.accountUpdate, (accountUpdate, assert) => { jsonString === JSON.stringify(AccountUpdate.toJSON(AccountUpdate.fromJSON(json))) ); - let fields = AccountUpdate.toFields(accountUpdate); - let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); - let recovered = AccountUpdate.fromFields(fields, auxiliary); - assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); + // TODO add back using `fromValue` + // let fields = AccountUpdate.toFields(accountUpdate); + // let auxiliary = AccountUpdate.toAuxiliary(accountUpdate); + // let recovered = AccountUpdate.fromFields(fields, auxiliary); + // assert(jsonString === JSON.stringify(AccountUpdate.toJSON(recovered))); }); test(Random.json.accountUpdate, (json) => { let jsonString = JSON.stringify(json); @@ -52,7 +53,7 @@ test.custom({ negative: true, timeBudget: 1000 })( AccountUpdate.fromJSON ); -const FeePayer = provableFromLayout< +const FeePayer = signableFromLayout< ZkappCommand['feePayer'], Json.ZkappCommand['feePayer'] >(jsLayout.ZkappCommand.entries.feePayer as any); diff --git a/src/lib/util/arrays.ts b/src/lib/util/arrays.ts new file mode 100644 index 0000000000..2a1a913eef --- /dev/null +++ b/src/lib/util/arrays.ts @@ -0,0 +1,14 @@ +import { assert } from '../gadgets/common.js'; + +export { chunk, chunkString }; + +function chunk(array: T[], size: number): T[][] { + assert(array.length % size === 0, 'invalid input length'); + return Array.from({ length: array.length / size }, (_, i) => + array.slice(size * i, size * (i + 1)) + ); +} + +function chunkString(str: string, size: number): string[] { + return chunk([...str], size).map((c) => c.join('')); +} diff --git a/src/lib/util/types.ts b/src/lib/util/types.ts index 600f5f705b..3256e60476 100644 --- a/src/lib/util/types.ts +++ b/src/lib/util/types.ts @@ -1,15 +1,23 @@ import { assert } from '../errors.js'; -export { Tuple, TupleN, AnyTuple }; +export { AnyFunction, Tuple, TupleN, AnyTuple, TupleMap }; + +type AnyFunction = (...args: any) => any; type Tuple = [T, ...T[]] | []; type AnyTuple = Tuple; +type TupleMap, B> = [ + ...{ + [i in keyof T]: B; + } +]; + const Tuple = { map, B>( tuple: T, f: (a: T[number]) => B - ): [...{ [i in keyof T]: B }] { + ): TupleMap { return tuple.map(f) as any; }, }; @@ -27,7 +35,7 @@ const TupleN = { map, B>( tuple: T, f: (a: T[number]) => B - ): [...{ [i in keyof T]: B }] { + ): TupleMap { return tuple.map(f) as any; }, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 45dd3a0424..9346973f72 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -22,7 +22,6 @@ import { FlexibleProvablePure, InferProvable, provable, - Struct, toConstant, } from './circuit_value.js'; import { Provable, getBlindingValue, memoizationContext } from './provable.js'; @@ -196,7 +195,8 @@ function wrapMethod( let id = memoizationContext.enter({ ...context, blindingValue }); let result: unknown; try { - result = method.apply(this, actualArgs.map(cloneCircuitValue)); + let clonedArgs = actualArgs.map(cloneCircuitValue); + result = method.apply(this, clonedArgs); } finally { memoizationContext.leave(id); } @@ -729,7 +729,7 @@ class SmartContract { verificationKey?: { data: string; hash: Field | string }; zkappKey?: PrivateKey; } = {}) { - let accountUpdate = this.newSelf(); + let accountUpdate = this.newSelf('deploy'); verificationKey ??= (this.constructor as typeof SmartContract) ._verificationKey; if (verificationKey === undefined) { @@ -873,10 +873,10 @@ super.init(); /** * Same as `SmartContract.self` but explicitly creates a new {@link AccountUpdate}. */ - newSelf(): AccountUpdate { + newSelf(methodName?: string): AccountUpdate { let inTransaction = Mina.currentTransaction.has(); let transactionId = inTransaction ? Mina.currentTransaction.id() : NaN; - let accountUpdate = selfAccountUpdate(this); + let accountUpdate = selfAccountUpdate(this, methodName); this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } diff --git a/src/mina-signer/MinaSigner.ts b/src/mina-signer/MinaSigner.ts index 36c94e3acb..d4dce0e2df 100644 --- a/src/mina-signer/MinaSigner.ts +++ b/src/mina-signer/MinaSigner.ts @@ -85,9 +85,9 @@ class Client { ) { throw Error('Public key not derivable from private key'); } - let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); + let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); dummy.feePayer.body.publicKey = publicKey; - dummy.memo = Memo.toBase58(Memo.emptyValue()); + dummy.memo = Memo.toBase58(Memo.empty()); let signed = signZkappCommand(dummy, privateKey, this.network); let ok = verifyZkappCommandSignature(signed, publicKey, this.network); if (!ok) throw Error('Could not sign a transaction with private key'); diff --git a/src/mina-signer/src/memo.ts b/src/mina-signer/src/memo.ts index 976c822ca6..04538c3965 100644 --- a/src/mina-signer/src/memo.ts +++ b/src/mina-signer/src/memo.ts @@ -62,10 +62,8 @@ const Memo = { hash, ...withBits(Binable, SIZE * 8), ...base58(Binable, versionBytes.userCommandMemo), - sizeInBytes() { - return SIZE; - }, - emptyValue() { + sizeInBytes: SIZE, + empty() { return Memo.fromString(''); }, toValidString(memo = '') { diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 69578ed193..07d8a37c62 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -180,7 +180,7 @@ function accountUpdateFromFeePayer({ body: { fee, nonce, publicKey, validUntil }, authorization: signature, }: FeePayer): AccountUpdate { - let { body } = AccountUpdate.emptyValue(); + let { body } = AccountUpdate.empty(); body.publicKey = publicKey; body.balanceChange = { magnitude: fee, sgn: Sign(-1) }; body.incrementNonce = Bool(true); diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 7538dbd9ff..e400f9c8d4 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -62,7 +62,7 @@ test(Random.json.publicKey, (publicKeyBase58) => { }); // empty account update -let dummy = AccountUpdate.emptyValue(); +let dummy = AccountUpdate.empty(); let dummySnarky = AccountUpdateSnarky.dummy(); expect(AccountUpdate.toJSON(dummy)).toEqual( AccountUpdateSnarky.toJSON(dummySnarky) diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 2fb520f02c..9743bda23b 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -122,7 +122,7 @@ for (let i = 0; i < 10; i++) { [0xffff_ffff_ffff_ffffn, 64], ], }, - AccountUpdate.toInput(AccountUpdate.emptyValue()), + AccountUpdate.toInput(AccountUpdate.empty()), ]; for (let msg of messages) { checkCanVerify(msg, key, publicKey); diff --git a/src/mina-signer/tests/zkapp.unit-test.ts b/src/mina-signer/tests/zkapp.unit-test.ts index 64d83c2bca..5c2aaf6a3f 100644 --- a/src/mina-signer/tests/zkapp.unit-test.ts +++ b/src/mina-signer/tests/zkapp.unit-test.ts @@ -11,7 +11,7 @@ import { mocks } from '../../bindings/crypto/constants.js'; const client = new Client({ network: 'testnet' }); let { publicKey, privateKey } = client.genKeys(); -let dummy = ZkappCommand.toJSON(ZkappCommand.emptyValue()); +let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); let dummySignature = Signature.toBase58(Signature.dummy()); // we construct a transaction which needs signing of the fee payer and another account update diff --git a/src/provable/curve-bigint.ts b/src/provable/curve-bigint.ts index 78e1e37a45..f4d18dc4a9 100644 --- a/src/provable/curve-bigint.ts +++ b/src/provable/curve-bigint.ts @@ -11,7 +11,7 @@ import { Bool, checkRange, Field, pseudoClass } from './field-bigint.js'; import { BinableBigint, ProvableBigint, - provable, + signable, } from '../bindings/lib/provable-bigint.js'; import { HashInputLegacy } from './poseidon-bigint.js'; @@ -79,7 +79,7 @@ let BinablePublicKey = withVersionNumber( * A public key, represented by a non-zero point on the Pallas curve, in compressed form { x, isOdd } */ const PublicKey = { - ...provable({ x: Field, isOdd: Bool }), + ...signable({ x: Field, isOdd: Bool }), ...withBase58(BinablePublicKey, versionBytes.publicKey), toJSON(publicKey: PublicKey) { @@ -137,7 +137,7 @@ let Base58PrivateKey = base58(BinablePrivateKey, versionBytes.privateKey); */ const PrivateKey = { ...Scalar, - ...provable(Scalar), + ...signable(Scalar), ...Base58PrivateKey, ...BinablePrivateKey, toPublicKey(key: PrivateKey) { diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index ea2d797c83..c23e0e0bc4 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -64,9 +64,7 @@ const Bool = pseudoClass( checkBool(x); return x; }, - sizeInBytes() { - return 1; - }, + sizeInBytes: 1, fromField(x: Field) { checkBool(x); return x as 0n | 1n; @@ -111,7 +109,7 @@ const Sign = pseudoClass( { ...ProvableBigint(checkSign), ...BinableBigint(1, checkSign), - emptyValue() { + empty() { return 1n; }, toInput(x: Sign): HashInput { diff --git a/src/provable/poseidon-bigint.ts b/src/provable/poseidon-bigint.ts index 2ef684d585..906ab2a2d5 100644 --- a/src/provable/poseidon-bigint.ts +++ b/src/provable/poseidon-bigint.ts @@ -7,7 +7,7 @@ import { createHashHelpers } from '../lib/hash-generic.js'; export { Poseidon, - Hash, + HashHelpers, HashInput, prefixes, packToFields, @@ -20,8 +20,8 @@ export { type HashInput = GenericHashInput; const HashInput = createHashInput(); -const Hash = createHashHelpers(Field, Poseidon); -let { hashWithPrefix } = Hash; +const HashHelpers = createHashHelpers(Field, Poseidon); +let { hashWithPrefix } = HashHelpers; const HashLegacy = createHashHelpers(Field, PoseidonLegacy); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 343d714c46..2c47b9dc63 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -25,6 +25,7 @@ import type { WasmFpSrs, WasmFqSrs, } from './bindings/compiled/node_bindings/plonk_wasm.cjs'; +import type { KimchiGateType } from './lib/gates.ts'; export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType, getWasm }; @@ -33,7 +34,6 @@ export { Snarky, Test, JsonGate, - KimchiGateType, MlPublicKey, MlPublicKeyVar, FeatureFlags, @@ -525,6 +525,7 @@ declare const Snarky: { }; }; + // TODO: implement in TS poseidon: { update( state: MlArray, @@ -541,27 +542,6 @@ declare const Snarky: { }; }; -declare enum KimchiGateType { - Zero, - Generic, - Poseidon, - CompleteAdd, - VarBaseMul, - EndoMul, - EndoMulScalar, - Lookup, - CairoClaim, - CairoInstruction, - CairoFlags, - CairoTransition, - RangeCheck0, - RangeCheck1, - ForeignFieldAdd, - ForeignFieldMul, - Xor16, - Rot64, -} - type GateType = | 'Zero' | 'Generic' diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts new file mode 100644 index 0000000000..3607494be1 --- /dev/null +++ b/src/tests/fake-proof.ts @@ -0,0 +1,96 @@ +import { + Mina, + PrivateKey, + SmartContract, + UInt64, + method, + ZkProgram, + verify, +} from 'o1js'; +import assert from 'assert'; + +const RealProgram = ZkProgram({ + name: 'real', + methods: { + make: { + privateInputs: [UInt64], + method(value: UInt64) { + let expected = UInt64.from(34); + value.assertEquals(expected); + }, + }, + }, +}); + +const FakeProgram = ZkProgram({ + name: 'fake', + methods: { + make: { privateInputs: [UInt64], method(_: UInt64) {} }, + }, +}); + +class RealProof extends ZkProgram.Proof(RealProgram) {} + +const RecursiveProgram = ZkProgram({ + name: 'broken', + methods: { + verifyReal: { + privateInputs: [RealProof], + method(proof: RealProof) { + proof.verify(); + }, + }, + }, +}); + +class RecursiveContract extends SmartContract { + @method verifyReal(proof: RealProof) { + proof.verify(); + } +} + +Mina.setActiveInstance(Mina.LocalBlockchain()); +let publicKey = PrivateKey.random().toPublicKey(); +let zkApp = new RecursiveContract(publicKey); + +await RealProgram.compile(); +await FakeProgram.compile(); +let { verificationKey: contractVk } = await RecursiveContract.compile(); +let { verificationKey: programVk } = await RecursiveProgram.compile(); + +// proof that should be rejected +const fakeProof = await FakeProgram.make(UInt64.from(99999)); +const dummyProof = await RealProof.dummy(undefined, undefined, 0); + +for (let proof of [fakeProof, dummyProof]) { + // zkprogram rejects proof + await assert.rejects(async () => { + await RecursiveProgram.verifyReal(proof); + }, 'recursive program rejects fake proof'); + + // contract rejects proof + await assert.rejects(async () => { + let tx = await Mina.transaction(() => zkApp.verifyReal(proof)); + await tx.prove(); + }, 'recursive contract rejects fake proof'); +} + +// proof that should be accepted +const realProof = await RealProgram.make(UInt64.from(34)); + +// zkprogram accepts proof +const brokenProof = await RecursiveProgram.verifyReal(realProof); +assert( + await verify(brokenProof, programVk.data), + 'recursive program accepts real proof' +); + +// contract accepts proof +let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); +let [contractProof] = await tx.prove(); +assert( + await verify(contractProof!, contractVk.data), + 'recursive contract accepts real proof' +); + +console.log('fake proof test passed 🎉'); diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index b5d0c1f447..67176a9f75 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -1,6 +1,6 @@ -import { Field, Group, Gadgets, Provable, Scalar } from 'o1js'; +import { Field, Group, Gadgets, Provable, Scalar, Hash, Bytes } from 'o1js'; -export { GroupCS, BitwiseCS }; +export { GroupCS, BitwiseCS, HashCS }; const GroupCS = constraintSystem('Group Primitive', { add() { @@ -84,6 +84,38 @@ const BitwiseCS = constraintSystem('Bitwise Primitive', { }, }); +const Bytes32 = Bytes(32); +const bytes32 = Bytes32.from([]); + +const HashCS = constraintSystem('Hashes', { + SHA256() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_256.hash(xs); + }, + + SHA384() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_384.hash(xs); + }, + + SHA512() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.SHA3_512.hash(xs); + }, + + Keccak256() { + let xs = Provable.witness(Bytes32.provable, () => bytes32); + Hash.Keccak256.hash(xs); + }, + + Poseidon() { + let xs = Array.from({ length: 32 }, (_, i) => i).map((x) => + Provable.witness(Field, () => Field(x)) + ); + Hash.Poseidon.hash(xs); + }, +}); + // mock ZkProgram API for testing function constraintSystem( diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index b7a582fcda..d2122996c9 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -202,17 +202,63 @@ "hash": "" } }, + "Hashes": { + "digest": "Hashes", + "methods": { + "SHA256": { + "rows": 14494, + "digest": "949539824d56622702d9ac048e8111e9" + }, + "SHA384": { + "rows": 14541, + "digest": "93dedf5824cab797d48e7a98c53c6bf3" + }, + "SHA512": { + "rows": 14588, + "digest": "3756008585b30a3951ed6455a7fbcdb0" + }, + "Keccak256": { + "rows": 14493, + "digest": "1ab08bd64002a0dd0a82f74df445de05" + }, + "Poseidon": { + "rows": 208, + "digest": "afa1f9920f1f657ab015c02f9b2f6c52" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, + "ecdsa-only": { + "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", + "methods": { + "verifySignedHash": { + "rows": 38888, + "digest": "f75dd9e49c88eb6097a7f3abbe543467" + } + }, + "verificationKey": { + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAHV05f+qac/ZBDmwqzaprv0J0hiho1m+s3yNkKQVSOkFyy3T9xnMBFjK62dF1KOp2k1Uvadd2KRyqwXiGN7JtQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09Ag9D9DKKoexOqr3N/Z3GGptvh3qvOPyxcWf475b+B/fTIwTQQC8ykkZ35HAVW3ZT6XDz0QFSmB0NJ8A+lkaTa0JF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" + } + }, "ecdsa": { - "digest": "812837fe4422b1e0eef563d0ea4db756880d91d823bf3c718114bb252c910db", + "digest": "baf90de64c1b6b5b5938a0b80a8fdb70bdbbc84a292dd7eccfdbce470c10b4c", "methods": { + "sha3": { + "rows": 14494, + "digest": "949539824d56622702d9ac048e8111e9" + }, "verifyEcdsa": { - "rows": 30615, - "digest": "4cfbbd9ee3d6ba141b7ba24a15889969" + "rows": 53386, + "digest": "5a234cff8ea48ce653cbd7efa2e1c241" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAK2yyyBQxwJz7O9wzMUdV0cuqOpuEoBs0A0YH8vifHsrc8GTF0AgBm/A2wdrPQB+hHe67QVSnaDKiYoXR3TzRCQgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MApMRQAtQgtEaZkMIsQaA6SxG75uU56yZRaFSazcM+OgO41jmYrOqxZJrJwiPLcniADnWAkPb06CuoY29KAa4cJfDR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "3139301773139601589155221041968477572715505359708488668851725819716203716299" + "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAA+OYudpfMnM3umoBqQoxTWfNZd6qs+yEusT1/Zn8mwWpEpLlhrEUyhWP5AW6XrVXTRFDrBUzfMPXR4WK6zFDRbXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tRp5+4JWVGK/zZHITJMRdMkalsW2dOqrVH7PSkjn6cwm4rs6BCduLjQE4r1GKVQsyhqr2DxOV+IX3iO8XzVueFJfH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", + "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" } } -} \ No newline at end of file +} diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index a051f21137..9c63c7e38e 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -3,8 +3,11 @@ import { Voting_ } from '../../src/examples/zkapps/voting/voting.js'; import { Membership_ } from '../../src/examples/zkapps/voting/membership.js'; import { HelloWorld } from '../../src/examples/zkapps/hello_world/hello_world.js'; import { TokenContract, createDex } from '../../src/examples/zkapps/dex/dex.js'; -import { ecdsaProgram } from '../../src/examples/zkprogram/ecdsa/ecdsa.js'; -import { GroupCS, BitwiseCS } from './plain-constraint-system.js'; +import { + ecdsa, + keccakAndEcdsa, +} from '../../src/examples/crypto/ecdsa/ecdsa.js'; +import { GroupCS, BitwiseCS, HashCS } from './plain-constraint-system.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; @@ -39,7 +42,9 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ createDex().Dex, GroupCS, BitwiseCS, - ecdsaProgram, + HashCS, + ecdsa, + keccakAndEcdsa, ]; let filePath = jsonPath ? jsonPath : './tests/vk-regression/vk-regression.json'; diff --git a/update-changelog.sh b/update-changelog.sh new file mode 100755 index 0000000000..701a1bc491 --- /dev/null +++ b/update-changelog.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# CHANGELOG Update Script for New Releases +# +# This script automates the process of updating the CHANGELOG.md in response to new releases. +# It performs the following actions: +# +# 1. Identifies the latest version number in the CHANGELOG (following semantic versioning). +# 2. Increments the patch segment of the version number for the new release. +# 3. Retrieves the current Git commit hash and truncates it for brevity. +# 4. Updates the CHANGELOG.md file: +# - Adds a new entry for the upcoming release using the incremented version number. +# - Updates the link for the [Unreleased] section to point from the current commit to HEAD. +# +# Usage: +# It should be run in the root directory of the repository where the CHANGELOG.md is located. +# Ensure that you have the necessary permissions to commit and push changes to the repository. + +# Step 1: Capture the latest version +latest_version=$(grep -oP '\[\K[0-9]+\.[0-9]+\.[0-9]+(?=\])' CHANGELOG.md | head -1) +echo "Latest version: $latest_version" + +# Step 2: Bump the patch version +IFS='.' read -r -a version_parts <<< "$latest_version" +let version_parts[2]+=1 +new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" +echo "New version: $new_version" + +# Step 3: Capture the current git commit and truncate it to the first 9 characters +current_commit=$(git rev-parse HEAD | cut -c 1-9) +echo "Current commit: $current_commit" + +# Step 4: Update the CHANGELOG +sed -i "s/\[Unreleased\](.*\.\.\.HEAD)/\[Unreleased\](https:\/\/github.com\/o1-labs\/o1js\/compare\/$current_commit...HEAD)\n\n## \[$new_version\](https:\/\/github.com\/o1-labs\/o1js\/compare\/1ad7333e9e...$current_commit)/" CHANGELOG.md From 55be454840a53e11a5e87a48ebef5cbcb7a1c2c2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 19:02:29 +0100 Subject: [PATCH 1190/1215] remove unused funciton --- src/lib/gadgets/foreign-field.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 1d07b14028..51b815dfc8 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -105,25 +105,6 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { return result; } -/** - * negate() deserves a special case because we can fix the overflow to -1 - * and know that a result in range is mapped to a result in range again. - */ -function negate(x: Field3, f: bigint) { - if (Field3.isConstant(x)) { - return sum([Field3.from(0n), x], [-1n], f); - } - // provable case - x = toVars(x); - let { result, overflow } = singleAdd(Field3.from(0n), x, -1n, f); - Gates.zero(...result); - multiRangeCheck(result); - - // fix the overflow to -1 - overflow.assertEquals(-1n); - return result; -} - /** * core building block for non-native addition * From 7a306ecfad8dc42f8fba9516292ba9fc1a8cd0db Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 19:02:58 +0100 Subject: [PATCH 1191/1215] dump vks --- tests/vk-regression/vk-regression.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index d2122996c9..bc18d2c213 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -232,33 +232,33 @@ } }, "ecdsa-only": { - "digest": "2113edb508f10afee42dd48aec81ac7d06805d76225b0b97300501136486bb30", + "digest": "3850f1a9ed21222b7428f1d916e53263c54fbe59ef2b84433822ce6e91dfe3b7", "methods": { "verifySignedHash": { - "rows": 38888, - "digest": "f75dd9e49c88eb6097a7f3abbe543467" + "rows": 30680, + "digest": "8459777a048742e889003c038a3c5bae" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAHV05f+qac/ZBDmwqzaprv0J0hiho1m+s3yNkKQVSOkFyy3T9xnMBFjK62dF1KOp2k1Uvadd2KRyqwXiGN7JtQwgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6P1Y8pKZHixBy1UrxqWGI+49oRtRFGw9CWS21EekuBFeu9RKI6yZLDiyRC2b3koFG+Kp6oq5Ej6Q8uargE09Ag9D9DKKoexOqr3N/Z3GGptvh3qvOPyxcWf475b+B/fTIwTQQC8ykkZ35HAVW3ZT6XDz0QFSmB0NJ8A+lkaTa0JF46ddCU9VJ1JmYsYa+MYEgKjZCvABbX9AEY2ggMr1cHaA49GrGul+Sj6pAvz4oyzaR8m7WAPMDuBtVwdbDtfju3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMu1Al7Tt/kOZrDznlS/szLlpAp2jISa8VWCmlAEPrustdNqQvptSsF6hikzXZVXg5f8pU4Gpa0TP0TRFvIYfmTyl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEugK31/fDN4Kw+NCeH0Vzjgzqkh2ZAAh43fdoZRSfQmEbnS/cpR7TEFAcYnin+2VaqiqOHiTKGS5FSlFiUE3j8gKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MAP9GUA7MRg39b77H7Dng7sccsDjjOy+d8lVRM7t0bXBverfVKycNJkfnL4YJK28eAfPKKXVAImbsLFiwkmmLRNfDR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "14476232789113674793628980675814050880886959683346583789420445656962585067758" } }, "ecdsa": { - "digest": "baf90de64c1b6b5b5938a0b80a8fdb70bdbbc84a292dd7eccfdbce470c10b4c", + "digest": "1e2e9efe37f21c726207ffcb4cd45b416c840e052c45112f90dc5a1d9d890499", "methods": { "sha3": { "rows": 14494, "digest": "949539824d56622702d9ac048e8111e9" }, "verifyEcdsa": { - "rows": 53386, - "digest": "5a234cff8ea48ce653cbd7efa2e1c241" + "rows": 45178, + "digest": "979498beb0bee3435acefc2781f99054" } }, "verificationKey": { - "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAA+OYudpfMnM3umoBqQoxTWfNZd6qs+yEusT1/Zn8mwWpEpLlhrEUyhWP5AW6XrVXTRFDrBUzfMPXR4WK6zFDRbXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tRp5+4JWVGK/zZHITJMRdMkalsW2dOqrVH7PSkjn6cwm4rs6BCduLjQE4r1GKVQsyhqr2DxOV+IX3iO8XzVueFJfH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", - "hash": "24853426911779666983119365864639019692354940105641954611250992067926077919455" + "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAAqeVSuDpzsMdHeMrEmxsUm8Se134AnmQ5P1w2NmMzAbh3EtuCw1KrUGZc82Txu8W3C1E7te6xAEey+ONDlI0jbXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tBTECYZ09Nh1FCxTwuOuXPLJr9o9uGQqe3TZ9/qmzjz9D7m97x8ncuAn2sZD4bhzLm/jLE9PLoOkAfM7QaunvPJfH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", + "hash": "18853186686572432164487682457354720373245151214885418412576372705202425675935" } } -} +} \ No newline at end of file From 32d50cf105d513a2a2b5736b1ce8a4d58977ff5c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 19:04:35 +0100 Subject: [PATCH 1192/1215] remove redundant assertion --- src/lib/gadgets/elliptic-curve.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 083ab64f53..00d730591c 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -367,7 +367,6 @@ function multiScalarMul( if (useGlv) { maxBits = Curve.Endo.decomposeMaxBits; - assert(maxBits < l2, 'decomposed scalars have to be < 2*88 bits'); // decompose scalars and handle signs let n2 = 2 * n; From 40c9947e42f7485e7e00d41d93bfe00180be60de Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 21:29:19 +0100 Subject: [PATCH 1193/1215] fix curve negation --- src/lib/gadgets/elliptic-curve.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 00d730591c..d2ee6eefc8 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -464,8 +464,8 @@ function negateIf(condition: Field, P: Point, f: bigint) { let y = Provable.if( Bool.Unsafe.ofField(condition), Field3.provable, - P.y, - ForeignField.negate(P.y, f) + ForeignField.negate(P.y, f), + P.y ); return { x: P.x, y }; } From dac5acb463521d2886ebf42929571863dcb23e87 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 18 Dec 2023 21:29:41 +0100 Subject: [PATCH 1194/1215] put stack trace limit back in run script (it's sometimes needed) --- run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run b/run index 5011793494..b039136688 100755 --- a/run +++ b/run @@ -1 +1 @@ -node --enable-source-maps src/build/run.js $@ +node --enable-source-maps --stack-trace-limit=1000 src/build/run.js $@ From 43cf2a022eefc6e672836f8a0a19dcd4a6c48af3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 07:40:47 +0100 Subject: [PATCH 1195/1215] dump vks for negation fix --- tests/vk-regression/vk-regression.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index bc18d2c213..5f27fdf9d4 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -232,20 +232,20 @@ } }, "ecdsa-only": { - "digest": "3850f1a9ed21222b7428f1d916e53263c54fbe59ef2b84433822ce6e91dfe3b7", + "digest": "1e6bef24a2e02b573363f3512822d401d53ec7220c8d5837cd49691c19723028", "methods": { "verifySignedHash": { "rows": 30680, - "digest": "8459777a048742e889003c038a3c5bae" + "digest": "5fe00efee7ecfb82b3ca69c8dd23d07f" } }, "verificationKey": { - "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEugK31/fDN4Kw+NCeH0Vzjgzqkh2ZAAh43fdoZRSfQmEbnS/cpR7TEFAcYnin+2VaqiqOHiTKGS5FSlFiUE3j8gKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MAP9GUA7MRg39b77H7Dng7sccsDjjOy+d8lVRM7t0bXBverfVKycNJkfnL4YJK28eAfPKKXVAImbsLFiwkmmLRNfDR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", - "hash": "14476232789113674793628980675814050880886959683346583789420445656962585067758" + "data": "AAAdmtvKeZvyx7UyPW6rIhb96GnTZEEywf8pGpbkt+QXNIm7oWxIDWYa4EWTiadEqzk8WXg3wiZmbXWcqBQU+uIoTiBnYTRcd7RsaAjbdbIbQJ9EuopFRFewZRx9qeQeEibNeMRcRMP4LdfS3AQRxhFZzN4HFa4MbtGs+Aja820cI9VFULH2/7BvD6JjpVWjVLvvo6zhO3S5axqfDh7QqtkPo3TLpand9OVvMHhTVlz/AV7rus5E/+0cv50MaEJ/wBfUh5XNLAlGgVi7FfWR6p9P72AAymyD3lUdecJyZmCREiVgPrTdFppkp45TefJWNTySkV9c5YzpNxQoXedZDvYP/5s4KBkfIeK+zB2yJC9eZ1ZDYfM88shGDYxmBtur9AkQ49QGquR+kYUI0lpXtuNMG+ZRy0FRJ8ci/TE+PIPIFnSiGcSOA3YM2G171LYf89abU2QUoQRHSP3PmmAOy/8CoRLVro7Nl6z/Ou0oZzX7RjOEo//LBqcSWa2S9X8TQz0R3uivbovTdq0rrba56SbEnK6LWItmBc6CubYWL7UzDbD3RZM6iRz1hqTHDzDz7UIWOzHgLqW9rjnZllQCyfsSAEce2RV0gBkOlsJXf/A50Yo1Y+0y0ZMB/g8wkRIs0p8RIff5piGXJPfSak+7+oCoV/CQoa0RkkegIKmjsjOyMAAgKzcNZhhPW5VfbcSYDpx5nVaU5pTEFl+2+RlcuhBpG1ksAWbD64AUKDjdyTWIC5Wn68AagPtG65V13eFS5LgkSfVtNXxGodg7SdP4AJmXpBgZfzMg4RW6Qje5ZFfrwRzoHPo0y7nO1hkaNLGV3Wvd3/pYiebXvyo+DdTZmaMbJpJaGSCysnovOrVUIpcn4h1hvA12jztQFQcbNHoVeZgslPxA54y9ynjhN7VZfT8lNXXIrRCpmPaxZW6Bw6Op/g6FGIs8TlruzZJRhz1lOLvl2FPvrUsFtz4yWTPjbT+VGsKuJFPvMuYybxq8pGyWVQN023uObDel47krlcQoH4MAggd39s50O0IaSbMgpJUWQVx+sxoXepe26SF5LQjWRDf7usrBVYTYoI9gDkVXMxLRmNnjQsKjg65fnQdhdLHyE/DR5DLlenSa0wQ3PXdv/C9LpDvkzJOLZs+/ZePd4YMI0+WuP2+6Xas4aNM+4JkNuHF5uMDcxgWID4TUy7Vdlzm3CVbhX15uBoKhuYWQgLr2rnVJ5SOZoDvlwJtcK2izLMYVAasejw4fvsehYGb88wvDbFxS6sM9gDSgTlavZRs95Qf+c1KpYf/jb8BxYNrwrqy8F++c1APDzfzQ/IbVLiaL28wkEy412qmXSjM+9hErKXFy8JIT/WBOIWMMg/7mMjvxngHnci+aYJZ6J+Lszh5zgo708vzO7fwaxC0wgd8anH3gFrbFnOg1hkmmoUEIgIwXh+ynuoZPOaoKNXNm1jOl8HpdFOG7vpQavC600YgzS2YGtY7K2WQ5GtN5ZTZBHPsUSir2yKSo9Le9CWXbDtn3SBDepWypwDa3YWKtNog+y10VmpL1N+RG3u1DXSuY7y9WZgkQ7tdvyx/Gjr91kjF0s3bt7vHIAZCtzNlRlWDBz3og0cSnEucCEuKR6dL2Mz+RuF1GmLoXZXapUjVG/82BjdAMAOxPlE67lEs+JWgnrVrA5NLJoL4DZ6+fhQKpNfk0uOrEfZIWR9Sau0IBwBxu6IYVm5/XAB19dt8MAuVcRdN/JGGzo0Hr3WVJuKzbAhuFwJZzcd1J1n4xO09ECT5NQdFSFXGsy8kIFjRNEOkLl+bAExePtGCt0w6cYqB0uCeX3lTI7ugIEgdStMtHFiWngJ218l8CuVrkwTJ7ZqHLtuJDiNqlLptkHWChDfw+IgDwz85dZrfBBzQrMRWranxQmisM+wx3vC+pLURRQHZJEasGCAElj0lTColrqQ/cXS7cBaqs1tBsQDGzKYMCMwsqL53fyxGCljVvljBa99+FpYfoUK+Fi0z6uEbem+luXRScr2yPB5I08lnBY23RmBb/pfSyBfbcmnmF5BkRlJTJKY7fQL/t9bFfywoquQe9e7OQvIjppA/FO7HmZS6hoOU+eS8+W94fEF2gvrowpTeqQHM6hLN9Qzl8niwZWUIyRCfyuzQnuSz/VP1K2sMFBKnZZNDcuBh1/xSFymOH6LfNKostvc6qHTIxrTjlH6952bo1bQl+mVvBUaJuRkYh12QbcyIyzcBFUYwaFazzkHXMof0O30oL3Q6wegTvJxTSZD5VCr5D26Myzoa0JBpqL0st9/MNGZe5a/+HW1qan/VtGA5nYkJcUzwKVqqlmZeuOZekFLGxlfp0lv9IQUQWtiU5uvd5HVoolEc/teUnx/IxYe01IDxX9cbmPMJnLYXJGSY=", + "hash": "13090090603196708539583054794074329820941412942119534377592583560995629520780" } }, "ecdsa": { - "digest": "1e2e9efe37f21c726207ffcb4cd45b416c840e052c45112f90dc5a1d9d890499", + "digest": "334c2efc6a82e87319cadb7f3e6f9edb55f8f2eab71e0bcf2451f3d5536de5fe", "methods": { "sha3": { "rows": 14494, @@ -253,12 +253,12 @@ }, "verifyEcdsa": { "rows": 45178, - "digest": "979498beb0bee3435acefc2781f99054" + "digest": "0b6ce4cc658bcca79b1b1373569aa9b9" } }, "verificationKey": { - "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAAqeVSuDpzsMdHeMrEmxsUm8Se134AnmQ5P1w2NmMzAbh3EtuCw1KrUGZc82Txu8W3C1E7te6xAEey+ONDlI0jbXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tBTECYZ09Nh1FCxTwuOuXPLJr9o9uGQqe3TZ9/qmzjz9D7m97x8ncuAn2sZD4bhzLm/jLE9PLoOkAfM7QaunvPJfH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", - "hash": "18853186686572432164487682457354720373245151214885418412576372705202425675935" + "data": "AAB/ZjMH6HxnifKVGWGw41mrmDWq4R7eG+tJRvszclriFqNFKZXi3LD9Z+2F/VSHlQD8mq3JCoD6tm3rlEQXheATALIHoglupcYB4X43h14qFKloG2kuKwLeDl6mRpeVwSmwE/Tv+P3K8KkyB9zmoT1eEt/ayPRm4EzrJb0EHzyrE0VhKfn6Xh48tBUpGk3JahLhsK+yOTjDBvwcZ/vsiNkGCtFbQv9rM7YZlpBk6NmFYzcnsfJOHyYyr6Xab+6UYjVt6aJGVLMgCv+qLekdsCsmVBwOWhEWwASVZ5WW5qWyOL613+zKXyQvqBcWWvg68CZk2rEjZvH64dPlVXV0hxYLb7p43WNgY+e8MR+RHA0/aGnvB4cwCKQw23n2B9bg+wxDwS7fK9/va/0HspgUvwGv8fNpdsYW1OgLnY1deUHhJc1+UaUFwHJVdB7Nx/WmQ0eq0s2wtLQGy0tpkDAkL5I9yd+qOSIbRoqSxBA7l/HWht0xGvVgNUOarOAeoqwfuBre8d3L5s5RR0HSmbjjP26/82rqxhQxiO7/O9lkCJ3GEeRqFbufY02mX+khOFw0uNU9XC6PXVmf01pMU7saHI4MAH9Akhy847NB7DCD8FCNcRyDJyDFJLlhdVhiVMAh55EYy513dTTwhKN9XKico081U+T2n7No0PiKZ32DBpDoLAPXVmyUfcILoFX2PQCQeSvOfAFM9mmARZ+OTRI8YpJuOzL9U2eIRqy34iz23PcKBmKQP0fGuuRJQOMEeRZp6sAjKJR/Temid2hEFxMIGnnwJGg/lEq5zXgo+e6Sdz4QeQEIT9PKi46gYqWxzyVsBJhtXX90uGNqo+ExnU3EGCvsB0/X9CQMBMKkHWk9zwkpQ3aQeVtNrzaLporX2ZQlPb03NgWHdeSk7V1VWa8KlCdsxVxj5tEOaWEX21jc6fMfUzegESsz7szcSEmbKYdzglTPAQ7DxXl0uhwGhAI9yPO3OtcqArq9TmnU/fkGG513bbwNYfAwr6P3u0QUs7GKZu8tRl04WobviAvYFoag3SDW1q6Vw5hze027jtn/cNmKGjLtwXvyz9YIeYEHNon9r2cWxrQOTvP/FhG/5+TsFMPsA5fH3hU0Fz1gmXw3qss0RmfBxLk+EdQC6m5yA/2B/8EPdrq5oHQPUyb6IHvNvUWn/Q/dMbZOKrMX2W+Htjjqtx4RTk4kxBypgD+8d4XRa8b3nN1iah85f+Qq6HcFzRB6E/Lye/74I01SUmwebrztjSxsz2qvHGoWtj+Ael25p2U3273WHAQtqv/koetaNRg7a17YCbEcLSCZTwI71dYDMiEFCaeijJL/ZJjRkev/LKK+dgBI8hi2ifiGzUsmNsJ1JUbNOjHS0et1ZljCsxnJ8GtPIcYU3+Az4UgJG6hcgZIYDmbwt/ElByu2obpX6gO2AD0g95NMIhOvlXXrfkRrygPlDiRIUUyO9h7z5mXB+A1iDUoi+m3hfbgLyjzXEDtJLZnX5k0gVVLjwGkk1Tgw0pJgWj1H+8sFAjKLB5T9yIEgYpLcXXbtV5zfaBXzPUFeLUAonv4S/gUhMA1QgSwXyCffgLRBrIXgwwiYepCmu1RYWXkLkTBwvSuTtKj9z4QGNdp5tBHrXKCOrYYN0chiDKIhFAa+PtylRK2fGeVnOZQSMcy04VIC5T+e0KR3pIt9PdJ72SHEYjmdNL+W9JL/fjz+co5Vxpz6p6RCwE1pL+kw4B0v1/BhJJB+NIv+GXTxOYeGx8K/P/3B9KUsbBykBQOvjcM8kJFIx5yry9idrtEGh5Ejw7UZ1W2sWIpFdKIAGUm54ir07a5Txkhy7PBIiyp5PRacp6V6S9aI8liE5LRC1qguXcKnbBg3QudnY/wUFwB6rJf7EBA2otA8PgEG8ICOg86XltPUqpVKUMBiu30jIa0VFS3O8BtuJvzdmVa8xK4K3ZP3/T4s4SAMOtyi6L4pxTJB9NHI0WnvQQeVAxlizlIu6VMwDdZuVtRJa0wbKTIDmc7lF+d2pD5uYOSLakc5h5jKRz386Dt9pYA577NKP+ioED2m+iv9bLPDIhDo6X589HxFZX4oxrzDI7m85rUvWBgNWqjZ9MpQSuui9yr2F5P8xtBsHDEIRi+wqX7Sui56cRuLZYaWvMVOIwkplHDIz0afncgI5BcA8KM4mJfpKosF42cWAMhijdDVAG234RIP6ogJCAcQu3q+q8uG2sYf655WE3+70zh0aiLvgQa1odo9FdEXV5HWs6Gh4hUDhA0ULlZVbJ+Mxg/XacBb+AnBxBpuSrIjfHaEtCkFKlDdF48Ny+HFVojxE4ZYuoNlXDDVkBi58aj2oMFDfaPB+bEJjxF7wpqjbts703mEckhLhQWpnnhNVbEyBGED8N4gIiA=", + "hash": "18381574995221799963215366500837755447342811019610547066832598459350935665488" } } } \ No newline at end of file From 108513cfe848f2a9c212c42cd500ba25b3be26ac Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 09:49:34 +0100 Subject: [PATCH 1196/1215] resolve dependency issue --- src/lib/hash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 4e077afdff..8bc2f3b7b7 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -6,7 +6,7 @@ import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; -import { Gadgets } from './gadgets/gadgets.js'; +import { Gadgets } from '../index.js'; // external API export { Poseidon, TokenSymbol }; From fc7067876577a8bdee7685a56450056a73034094 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 10:21:31 +0100 Subject: [PATCH 1197/1215] resolve dependency issue, yet again --- src/lib/hash.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 8bc2f3b7b7..8f51d97d0f 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -6,7 +6,7 @@ import { Provable } from './provable.js'; import { MlFieldArray } from './ml/fields.js'; import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; -import { Gadgets } from '../index.js'; +import { rangeCheckN } from './gadgets/range-check.js'; // external API export { Poseidon, TokenSymbol }; @@ -167,7 +167,7 @@ const TokenSymbolPure: ProvableExtended< return 1; }, check({ field }: TokenSymbol) { - Gadgets.rangeCheckN(48, field); + rangeCheckN(48, field); }, toJSON({ symbol }) { return symbol; From a8ff0aa9945842262b69c15bb8fa31afe6bb8085 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 10:26:11 +0100 Subject: [PATCH 1198/1215] return UInt32 instead of Field --- src/lib/int.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 2da45696db..9bae3afdf1 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -871,7 +871,7 @@ class UInt32 extends CircuitValue { * ``` */ and(x: UInt32) { - return Gadgets.and(this.value, x.value, UInt32.NUM_BITS); + return new UInt32(Gadgets.and(this.value, x.value, UInt32.NUM_BITS)); } /** From d4f27be6d372bdfbb072212c756c9a0f72b09334 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 11:15:23 +0100 Subject: [PATCH 1199/1215] changelog --- CHANGELOG.md | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4fc6f187..d4bb957ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,26 +19,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) -### Added - -- Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 -- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 - - For an example, see `./src/examples/crypto/ecdsa` - -## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) - -### Breaking changes - -- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1292 [@rpanic](https://github.com/rpanic) - -### Added - -- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 -- `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 -- `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 -- `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 -- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288 - ### Breaking changes - Rename `Gadgets.rotate()` to `Gadgets.rotate64()` to better reflect the amount of bits the gadget operates on. https://github.com/o1-labs/o1js/pull/1259 @@ -46,6 +26,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 +- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 + + - For an example, see `./src/examples/crypto/ecdsa` + - `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.leftShift32()` for left shift over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 @@ -59,6 +44,20 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift()` - bitwise AND via `{UInt32, UInt64}.and()` +## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) + +### Breaking changes + +- `ZkProgram.compile()` now returns the verification key and its hash, to be consistent with `SmartContract.compile()` https://github.com/o1-labs/o1js/pull/1292 [@rpanic](https://github.com/rpanic) + +### Added + +- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985 +- `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240 +- `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262 +- `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262 +- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288 + ### Changed - Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions. [@LuffySama-Dev](https://github.com/LuffySama-Dev) From e2785ab145b78ed1165f0a894368f26f70c65333 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 19 Dec 2023 11:22:27 +0100 Subject: [PATCH 1200/1215] fix correct bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 61c75c037d..7af5696e11 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 61c75c037db058231f4e1b13a4743178b95d0aa0 +Subproject commit 7af5696e110243b3a6e1760087384395042710d4 From 45226204e8d04d16086d7405f8942f37f4164354 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 12:05:56 +0100 Subject: [PATCH 1201/1215] Bytes.random --- src/lib/provable-types/bytes.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index 992e10bd1f..9bde301e69 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -4,6 +4,7 @@ import { assert } from '../gadgets/common.js'; import { chunkString } from '../util/arrays.js'; import { Provable } from '../provable.js'; import { UInt8 } from '../int.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; export { Bytes, createBytes, FlexibleBytes }; @@ -64,6 +65,14 @@ class Bytes { return this.from(bytes); } + /** + * Create random {@link Bytes} using secure builtin randomness. + */ + static random() { + let bytes = randomBytes(this.size); + return this.from(bytes); + } + /** * Create {@link Bytes} from a hex string. * From abb8202005098be1768bc8d53926857e385ff9c6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 12:06:07 +0100 Subject: [PATCH 1202/1215] keccak witness gen benchmark --- src/examples/benchmarks/keccak-witness.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/examples/benchmarks/keccak-witness.ts diff --git a/src/examples/benchmarks/keccak-witness.ts b/src/examples/benchmarks/keccak-witness.ts new file mode 100644 index 0000000000..cedd6982ca --- /dev/null +++ b/src/examples/benchmarks/keccak-witness.ts @@ -0,0 +1,10 @@ +import { Hash, Bytes, Provable } from 'o1js'; + +let Bytes32 = Bytes(32); + +console.time('keccak witness'); +Provable.runAndCheck(() => { + let bytes = Provable.witness(Bytes32.provable, () => Bytes32.random()); + Hash.Keccak256.hash(bytes); +}); +console.timeEnd('keccak witness'); From 847bf36267e2cc0bfe389dd829e6a42487e8ae7a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 14:12:32 +0100 Subject: [PATCH 1203/1215] fix bitwise unit test --- src/lib/gadgets/bitwise.unit-test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index e3444552a0..f6f186e941 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -78,6 +78,7 @@ let Bitwise = ZkProgram({ leftShift32: { privateInputs: [Field], method(a: Field) { + Gadgets.rangeCheck32(a); return Gadgets.leftShift32(a, 12); }, }, @@ -138,7 +139,9 @@ await Bitwise.compile(); ); }); -await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs: 3 })( +const runs = 2; + +await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs })( (x, y) => { return x ^ y; }, @@ -148,7 +151,7 @@ await equivalentAsync({ from: [uint(64), uint(64)], to: field }, { runs: 3 })( } ); -await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeField], to: field }, { runs })( (x) => { return Fp.not(x, 254); }, @@ -157,7 +160,7 @@ await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( return proof.publicOutput; } ); -await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( +await equivalentAsync({ from: [maybeField], to: field }, { runs })( (x) => { if (x > 2n ** 254n) throw Error('Does not fit into 254 bit'); return Fp.not(x, 254); @@ -168,10 +171,7 @@ await equivalentAsync({ from: [maybeField], to: field }, { runs: 3 })( } ); -await equivalentAsync( - { from: [maybeField, maybeField], to: field }, - { runs: 3 } -)( +await equivalentAsync({ from: [maybeField, maybeField], to: field }, { runs })( (x, y) => { if (x >= 2n ** 64n || y >= 2n ** 64n) throw Error('Does not fit into 64 bits'); @@ -183,7 +183,7 @@ await equivalentAsync( } ); -await equivalentAsync({ from: [field], to: field }, { runs: 3 })( +await equivalentAsync({ from: [field], to: field }, { runs })( (x) => { if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); return Fp.rot(x, 12n, 'left'); @@ -194,7 +194,7 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); -await equivalentAsync({ from: [uint(32)], to: uint(32) }, { runs: 30 })( +await equivalentAsync({ from: [uint(32)], to: uint(32) }, { runs })( (x) => { return Fp.rot(x, 12n, 'left', 32n); }, @@ -204,7 +204,7 @@ await equivalentAsync({ from: [uint(32)], to: uint(32) }, { runs: 30 })( } ); -await equivalentAsync({ from: [field], to: field }, { runs: 3 })( +await equivalentAsync({ from: [field], to: field }, { runs })( (x) => { if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); return Fp.leftShift(x, 12); @@ -215,9 +215,9 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); -await equivalentAsync({ from: [field], to: field }, { runs: 3 })( +await equivalentAsync({ from: [field], to: field }, { runs })( (x) => { - if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); + if (x >= 1n << 32n) throw Error('Does not fit into 32 bits'); return Fp.leftShift(x, 12, 32); }, async (x) => { @@ -226,7 +226,7 @@ await equivalentAsync({ from: [field], to: field }, { runs: 3 })( } ); -await equivalentAsync({ from: [field], to: field }, { runs: 3 })( +await equivalentAsync({ from: [field], to: field }, { runs })( (x) => { if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits'); return Fp.rightShift(x, 12); From 1c76a2e005c75ff62bbf654a137feb95a4030fbd Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 12:08:17 +0100 Subject: [PATCH 1204/1215] unit-test keccak in provable version --- src/lib/keccak.unit-test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 2e4c160b3f..aad02e2342 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -1,6 +1,10 @@ import { Keccak } from './keccak.js'; import { ZkProgram } from './proof_system.js'; -import { equivalent, equivalentAsync } from './testing/equivalent.js'; +import { + equivalentProvable, + equivalent, + equivalentAsync, +} from './testing/equivalent.js'; import { keccak_224, keccak_256, @@ -47,13 +51,13 @@ for (let length of lengths) { let inputBytes = bytes(preimageLength); let outputBytes = bytes(length / 8); - equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( testImplementations.sha3[length], (x) => Keccak.nistSha3(length, x), `sha3 ${length}` ); - equivalent({ from: [inputBytes], to: outputBytes, verbose: true })( + equivalentProvable({ from: [inputBytes], to: outputBytes, verbose: true })( testImplementations.preNist[length], (x) => Keccak.preNist(length, x), `keccak ${length}` From 6270ce91f194b29d6865063ff4467ca24ec3a761 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 14:19:41 +0100 Subject: [PATCH 1205/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7d5d3d74c0..2ccc5209bf 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7d5d3d74c04eb8ed0e8ad728148e6c3a56047bce +Subproject commit 2ccc5209bf5f3d0295b125da9b227b0decf82b74 From 6c6e466360a10456bef8558784a94e757f82d56e Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 15:49:01 +0100 Subject: [PATCH 1206/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 4aa040e262..e9dac8627d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4aa040e26248696114eea50f6cae363351815f5e +Subproject commit e9dac8627d7318d4b0c858b552483db45134709f From e91e0b08a4d4da10ac2dcb985ae613cc277e92d0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 15:54:47 +0100 Subject: [PATCH 1207/1215] trigger CI --- src/lib/field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/field.ts b/src/lib/field.ts index 891390619d..a46c00b840 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -280,7 +280,7 @@ class Field { } /** - * Add a "field-like" value to this {@link Field} element. + * Add a field-like value to this {@link Field} element. * * @example * ```ts From 19115a159bbb0eeb97b190ef810e8d3b36743cc3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 19 Dec 2023 18:45:21 +0000 Subject: [PATCH 1208/1215] 0.15.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 085d249609..24af729ad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.15.0", + "version": "0.15.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.15.0", + "version": "0.15.1", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index f58dd76fa1..ac4a5736e9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.15.0", + "version": "0.15.1", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ From e5f22757a984c97a0d6940dab51070551665587c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 19 Dec 2023 18:45:28 +0000 Subject: [PATCH 1209/1215] Update CHANGELOG for new version v0.15.1 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a07860b138..dabec35706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/7acf19d0d...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/19115a159...HEAD) + +## [0.15.1](https://github.com/o1-labs/o1js/compare/1ad7333e9e...19115a159) ### Breaking changes From f6d991fad783547d9c790ce1b21914553a618662 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 19 Dec 2023 12:36:55 -0800 Subject: [PATCH 1210/1215] trigger CI From 8cba17ac4bc24e0923af7a157385c9d844fefd76 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 20 Dec 2023 11:04:43 +0100 Subject: [PATCH 1211/1215] changelog --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dabec35706..2b30d0e852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Non-native elliptic curve operations exposed through `createForeignCurve()` class factory https://github.com/o1-labs/o1js/pull/1007 -- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 - +- **ECDSA signature verification** exposed through `createEcdsa()` class factory https://github.com/o1-labs/o1js/pull/1240 https://github.com/o1-labs/o1js/pull/1007 https://github.com/o1-labs/o1js/pull/1307 - For an example, see `./src/examples/crypto/ecdsa` - +- **Keccak/SHA3 hash function** exposed on `Keccak` namespace https://github.com/o1-labs/o1js/pull/1291 +- `Hash` namespace which holds all hash functions https://github.com/o1-labs/o1js/pull/999 + - `Bytes`, provable type to hold a byte array, which serves as input and output for Keccak variants + - `UInt8`, provable type to hold a single byte, which is constrained to be in the 0 to 255 range - `Gadgets.rotate32()` for rotation over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.leftShift32()` for left shift over 32 bit values https://github.com/o1-labs/o1js/pull/1259 - `Gadgets.divMod32()` division modulo 2^32 that returns the remainder and quotient of the operation https://github.com/o1-labs/o1js/pull/1259 @@ -43,10 +45,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - bitwise LEFTSHIFT via `{UInt32, UInt64}.leftShift()` - bitwise RIGHTSHIFT via `{UInt32, UInt64}.rightShift()` - bitwise AND via `{UInt32, UInt64}.and()` +- Example for using actions to store a map data structure https://github.com/o1-labs/o1js/pull/1300 +- `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `summary()` method to return a summary of the constraints used by a method https://github.com/o1-labs/o1js/pull/1007 ### Fixed - Fix stack overflows when calling provable methods with large inputs https://github.com/o1-labs/o1js/pull/1334 +- Fix `Local.setProofsEnabled()` which would not get picked up by `deploy()` https://github.com/o1-labs/o1js/pull/1330 +- Remove usage of private class fields in core types like `Field`, for better type compatibility between different o1js versions https://github.com/o1-labs/o1js/pull/1319 ## [0.15.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...7acf19d0d) From 9ae2dfed893e3769030fdd29407d59e86239f4f0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 20 Dec 2023 12:00:30 +0100 Subject: [PATCH 1212/1215] mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index cb151f6a7d..2a968c8347 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit cb151f6a7d34913090b9c19b79f8872389be5c04 +Subproject commit 2a968c83477ed9f9e3b30a02cc357e541b76dcac From e76b97f6fc780e693b8240317793ab3cb20b7bb2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 20 Dec 2023 13:08:13 +0100 Subject: [PATCH 1213/1215] submodules --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index d93087e42e..fd92331289 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d93087e42e15beb18890f53584c38cb3ceac7c8a +Subproject commit fd923312894b408704916e65e26706bbc7def62a diff --git a/src/mina b/src/mina index 6acef0df1d..8cb46511e6 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 6acef0df1de82410f4932853b570f778701c09cf +Subproject commit 8cb46511e61d0d15bb70a6e819dcdde0b84d97ad From 3fa0a45f6d0bc4b1e8f9fd5c11d1215fe3680f8a Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 20 Dec 2023 15:08:53 +0100 Subject: [PATCH 1214/1215] make hello world contract deterministic --- src/examples/zkapps/hello_world/hello_world.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/examples/zkapps/hello_world/hello_world.ts b/src/examples/zkapps/hello_world/hello_world.ts index 4ccd77bdd9..77eb191d9d 100644 --- a/src/examples/zkapps/hello_world/hello_world.ts +++ b/src/examples/zkapps/hello_world/hello_world.ts @@ -1,6 +1,8 @@ import { Field, PrivateKey, SmartContract, State, method, state } from 'o1js'; -export const adminPrivateKey = PrivateKey.random(); +export const adminPrivateKey = PrivateKey.fromBase58( + 'EKFcef5HKXAn7V2rQntLiXtJr15dkxrsrQ1G4pnYemhMEAWYbkZW' +); export const adminPublicKey = adminPrivateKey.toPublicKey(); export class HelloWorld extends SmartContract { From f06284493a86d70659ca41f7ae6acb33f64e0b50 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 11 Jan 2024 15:49:42 +0100 Subject: [PATCH 1215/1215] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 4b6d70b9b4..1db0524c5c 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4b6d70b9b47c08396e4965ca0cba2c194c944d01 +Subproject commit 1db0524c5c7c93f0df9db3fb3bdfa0414bdaf90b