From a637e7cdd5a3589efead01ef6ab50e41037885a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 28 Jun 2023 13:07:11 +0200 Subject: [PATCH 001/101] 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 002/101] 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 003/101] 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 addf84cd7b1f03f299c5246eb33c442c02499af3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Jun 2023 15:59:04 +0200 Subject: [PATCH 004/101] 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 005/101] 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 006/101] 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 007/101] 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 008/101] 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 6f0a8887c38dab3e443c8e6cf71a7ba79ead44ef Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 30 Jun 2023 10:58:49 +0200 Subject: [PATCH 009/101] 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 010/101] 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 011/101] 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 012/101] 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 013/101] 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 014/101] 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 015/101] 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 36ad8953057e99f4b5f2ad1d4fd8aa439d7eda8c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 5 Jul 2023 13:00:03 +0200 Subject: [PATCH 016/101] 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 9b5cf28ecfc17cb7cc2d4e8513c3e38ca3cdc994 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 12 Jul 2023 22:23:15 +0200 Subject: [PATCH 017/101] 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 018/101] 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 019/101] 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 020/101] 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 021/101] 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 022/101] 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 023/101] 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 024/101] 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 025/101] 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 026/101] 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 027/101] 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 028/101] 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 029/101] 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 030/101] 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 031/101] 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 5384087a276439f405f0d23ec479f5a34813b670 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Nov 2023 22:14:03 +0100 Subject: [PATCH 032/101] 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 033/101] 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 034/101] 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 035/101] 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 036/101] 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 037/101] 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 038/101] 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 039/101] 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 040/101] 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 d9353d4eeb8378c2340f3aecf3cee089ddea6580 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 09:20:59 +0100 Subject: [PATCH 041/101] 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 042/101] 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 043/101] 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 044/101] 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 045/101] 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 8c09689689c5e65d6309c22f29e21e0f7976ab4e Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 10:40:12 +0100 Subject: [PATCH 046/101] 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 047/101] 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 048/101] 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 da21b199359c97aacba2b857dc29f2842f25d3ef Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 13:13:04 +0100 Subject: [PATCH 049/101] 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 050/101] 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 051/101] 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 052/101] 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 053/101] 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 054/101] 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 055/101] 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 056/101] 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 e1282d18e429be50d0f6fb3543ff68408a579a9c Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 14:15:45 +0100 Subject: [PATCH 057/101] 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 058/101] 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 059/101] 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 060/101] 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 061/101] 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 062/101] 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 063/101] 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 064/101] 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 065/101] 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 5095fac473537f6c3f9292d84b92b5ad6c945347 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 16:51:18 +0100 Subject: [PATCH 066/101] 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 067/101] 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": "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", - "hash": "21152043351405736424630704375244880683906889913335338686836578165782029001213" + "data": "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", + "hash": "692488770088381683600018326540220594385299569804743037992171368071788655058" } } } \ No newline at end of file From e9c21b0f41aa26475a1a9a41825076606bc3922b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 21:49:42 +0100 Subject: [PATCH 068/101] 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 069/101] 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 070/101] 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 071/101] 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 072/101] 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": "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", - "hash": "692488770088381683600018326540220594385299569804743037992171368071788655058" + "data": "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", + "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 073/101] 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 074/101] 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 075/101] 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 076/101] 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 07a1fde0392397b3864abd603b2612a23a7af8d3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 08:35:26 +0100 Subject: [PATCH 077/101] 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 078/101] 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 079/101] 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 080/101] 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 081/101] 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 082/101] 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 083/101] 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 084/101] 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 085/101] 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 086/101] 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 087/101] 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 088/101] 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 40cb3740a8f355085923fc1a220fd260a3e53229 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 10:26:46 +0100 Subject: [PATCH 089/101] 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 090/101] 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 091/101] 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 092/101] 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 093/101] 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 094/101] 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 095/101] 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 096/101] 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 097/101] 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 098/101] 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 31282ea00f1fc37c44bbfcf9394bbe75c69fe0df Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Dec 2023 13:57:56 +0100 Subject: [PATCH 099/101] 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 db8f63afd45ae80d3e8eaa3eb9bd21d38ff17766 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Dec 2023 20:53:21 +0100 Subject: [PATCH 100/101] 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": "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", + "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 62091b8025ace31d7f001f21bbba7499b75d65ab Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 7 Dec 2023 08:12:18 +0100 Subject: [PATCH 101/101] 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": "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", - "hash": "5175094130394897519519312310597261032080672241911883655552182168739967574124" + "data": "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", + "hash": "10504586047480864396273137275551599454708712068910013426206550544367939284599" } } } \ No newline at end of file