From 3491201f901df6e221a06143b223447f224d5fd7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:33:11 +0100 Subject: [PATCH 1/9] foreign field: fix division type --- src/lib/foreign-field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 55cd105160..06304095a7 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -450,7 +450,7 @@ class ForeignFieldWithMul extends ForeignField { * z.mul(y).assertEquals(x); * ``` */ - div(y: ForeignField | bigint | number) { + div(y: AlmostForeignField | bigint | number) { const p = this.modulus; let z = Gadgets.ForeignField.div(this.value, toLimbs(y, p), p); return new this.Constructor.AlmostReduced(z); From da252c0239920e17fef3506462c5ce0b9c59d581 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:33:27 +0100 Subject: [PATCH 2/9] twisted edwards: addition --- src/examples/crypto/twisted-edwards.ts | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/examples/crypto/twisted-edwards.ts diff --git a/src/examples/crypto/twisted-edwards.ts b/src/examples/crypto/twisted-edwards.ts new file mode 100644 index 0000000000..119ca0e2de --- /dev/null +++ b/src/examples/crypto/twisted-edwards.ts @@ -0,0 +1,59 @@ +/** + * Implementation of twisted Edwards curves for Ed25529. + * + * https://en.wikipedia.org/wiki/Twisted_Edwards_curve + * https://en.wikipedia.org/wiki/EdDSA#Ed25519 + */ +import { Provable, Struct, createForeignField } from 'o1js'; + +const p = 2n ** 255n - 19n; +const FpUnreduced = createForeignField(p); +class Fp extends FpUnreduced.AlmostReduced {} + +type Point = { x: Fp; y: Fp }; +const Point = Struct({ x: Fp.provable, y: Fp.provable }); + +/** Curve equation: + * + * -x^2 + y^2 = 1 + d * x^2 * y^2 + */ +const d = Fp.from(-121665n).div(121666n); + +function add(p: Point, q: Point): Point { + let { x: x1, y: y1 } = p; + let { x: x2, y: y2 } = q; + + // x3 = (x1 * y2 + y1 * x2) / (1 + d * x1 * x2 * y1 * y2) + // y3 = (y1 * y2 + x1 * x2) / (1 - d * x1 * x2 * y1 * y2) + let x1x2 = x1.mul(x2); + let y1y2 = y1.mul(y2); + let x1y2 = x1.mul(y2); + let y1x2 = y1.mul(x2); + let x3Num = x1y2.add(y1x2); + let y3Num = y1y2.add(x1x2); + + let [x1x2r, y1y2r, x3Numr] = Fp.assertAlmostReduced(x1x2, y1y2, x3Num); + let x1x2y1y2 = x1x2r.mul(y1y2r); + + let [y3Numr, x1x2y1y2r] = Fp.assertAlmostReduced(y3Num, x1x2y1y2); + let dx1x2y1y2 = d.mul(x1x2y1y2r); + let x3Denom = Fp.from(1n).add(dx1x2y1y2); + let y3Denom = Fp.from(1n).sub(dx1x2y1y2); + + let [x3Denomr, y3Denomr] = Fp.assertAlmostReduced(x3Denom, y3Denom); + + let x3 = x3Numr.div(x3Denomr); + let y3 = y3Numr.div(y3Denomr); + + let [x3r, y3r] = Fp.assertAlmostReduced(x3, y3); + + return { x: x3r, y: y3r }; +} + +let cs = Provable.constraintSystem(() => { + let p = Provable.witness(Point, Point.empty); + let q = Provable.witness(Point, Point.empty); + + let r = add(p, q); +}); +console.log(cs); From 4138dc9af63bd49ef92a0290abf9ec5c30765b46 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 14:12:34 +0100 Subject: [PATCH 3/9] expose cs printing --- src/examples/constraint_system.ts | 4 +- src/lib/provable-context.ts | 69 +++++++++++++++++++++++++++- src/lib/testing/constraint-system.ts | 56 +--------------------- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/examples/constraint_system.ts b/src/examples/constraint_system.ts index 6acb7fde53..f2da63ad3b 100644 --- a/src/examples/constraint_system.ts +++ b/src/examples/constraint_system.ts @@ -2,7 +2,7 @@ import { Field, Poseidon, Provable } from 'o1js'; let hash = Poseidon.hash([Field(1), Field(-1)]); -let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { +let { rows, digest, publicInputSize, print } = Provable.constraintSystem(() => { let x = Provable.witness(Field, () => Field(1)); let y = Provable.witness(Field, () => Field(-1)); x.add(y).assertEquals(Field(0)); @@ -10,5 +10,5 @@ let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { z.assertEquals(hash); }); -console.log(JSON.stringify(gates)); +print(); console.log({ rows, digest, publicInputSize }); diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 076e9f8300..0ce0030556 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -2,6 +2,7 @@ import { Context } from './global-context.js'; import { Gate, JsonGate, Snarky } from '../snarky.js'; import { parseHexString } from '../bindings/crypto/bigint-helpers.js'; import { prettifyStacktrace } from './errors.js'; +import { Fp } from '../bindings/crypto/finite_field.js'; // internal API export { @@ -17,6 +18,7 @@ export { inCompile, inCompileMode, gatesFromJson, + printGates, }; // global circuit-related context @@ -94,7 +96,16 @@ function constraintSystem(f: () => T) { result = f(); }); let { gates, publicInputSize } = gatesFromJson(json); - return { rows, digest, result: result! as T, gates, publicInputSize }; + return { + rows, + digest, + result: result! as T, + gates, + publicInputSize, + print() { + printGates(gates); + }, + }; } catch (error) { throw prettifyStacktrace(error); } finally { @@ -106,8 +117,62 @@ function constraintSystem(f: () => T) { function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: hexCoeffs }) => { - let coeffs = hexCoeffs.map(hex => parseHexString(hex).toString()); + let coeffs = hexCoeffs.map((hex) => parseHexString(hex).toString()); return { type: typ, wires, coeffs }; }); return { publicInputSize: cs.public_input_size, gates }; } + +// print a constraint system + +function printGates(gates: Gate[]) { + for (let i = 0, n = gates.length; i < n; i++) { + let { type, wires, coeffs } = gates[i]; + console.log( + i.toString().padEnd(4, ' '), + type.padEnd(15, ' '), + coeffsToPretty(type, coeffs).padEnd(30, ' '), + wiresToPretty(wires, i) + ); + } + console.log(); +} + +let minusRange = Fp.modulus - (1n << 64n); + +function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { + if (coeffs.length === 0) return ''; + if (type === 'Generic' && coeffs.length > 5) { + let first = coeffsToPretty(type, coeffs.slice(0, 5)); + let second = coeffsToPretty(type, coeffs.slice(5)); + return `${first} ${second}`; + } + if (type === 'Poseidon' && coeffs.length > 3) { + return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; + } + let str = coeffs + .map((c) => { + let c0 = BigInt(c); + if (c0 > minusRange) c0 -= Fp.modulus; + let cStr = c0.toString(); + if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; + return cStr; + }) + .join(' '); + return `[${str}]`; +} + +function wiresToPretty(wires: Gate['wires'], row: number) { + let strWires: string[] = []; + let n = wires.length; + for (let col = 0; col < n; col++) { + let wire = wires[col]; + if (wire.row === row && wire.col === col) continue; + if (wire.row === row) { + strWires.push(`${col}->${wire.col}`); + } else { + strWires.push(`${col}->(${wire.row},${wire.col})`); + } + } + return strWires.join(', '); +} diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index d39c6f6991..2922afa11d 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -12,6 +12,7 @@ import { Tuple } from '../util/types.js'; import { Random } from './random.js'; import { test } from './property.js'; import { Undefined, ZkProgram } from '../proof_system.js'; +import { printGates } from '../provable-context.js'; export { constraintSystem, @@ -27,7 +28,6 @@ export { withoutGenerics, print, repeat, - printGates, ConstraintSystemTest, }; @@ -446,57 +446,3 @@ type CsParams>> = { [k in keyof In]: InferCsVar; }; type TypeAndValue = { type: Provable; value: T }; - -// print a constraint system - -function printGates(gates: Gate[]) { - for (let i = 0, n = gates.length; i < n; i++) { - let { type, wires, coeffs } = gates[i]; - console.log( - i.toString().padEnd(4, ' '), - type.padEnd(15, ' '), - coeffsToPretty(type, coeffs).padEnd(30, ' '), - wiresToPretty(wires, i) - ); - } - console.log(); -} - -let minusRange = Field.ORDER - (1n << 64n); - -function coeffsToPretty(type: Gate['type'], coeffs: Gate['coeffs']): string { - if (coeffs.length === 0) return ''; - if (type === 'Generic' && coeffs.length > 5) { - let first = coeffsToPretty(type, coeffs.slice(0, 5)); - let second = coeffsToPretty(type, coeffs.slice(5)); - return `${first} ${second}`; - } - if (type === 'Poseidon' && coeffs.length > 3) { - return `${coeffsToPretty(type, coeffs.slice(0, 3)).slice(0, -1)} ...]`; - } - let str = coeffs - .map((c) => { - let c0 = BigInt(c); - if (c0 > minusRange) c0 -= Field.ORDER; - let cStr = c0.toString(); - if (cStr.length > 4) return `${cStr.slice(0, 4)}..`; - return cStr; - }) - .join(' '); - return `[${str}]`; -} - -function wiresToPretty(wires: Gate['wires'], row: number) { - let strWires: string[] = []; - let n = wires.length; - for (let col = 0; col < n; col++) { - let wire = wires[col]; - if (wire.row === row && wire.col === col) continue; - if (wire.row === row) { - strWires.push(`${col}->${wire.col}`); - } else { - strWires.push(`${col}->(${wire.row},${wire.col})`); - } - } - return strWires.join(', '); -} From cb58676ae1e33dc8f4a6901a2ddf82743f8dc8bf Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:59:09 +0100 Subject: [PATCH 4/9] type fix --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 4efa3165ea..4fd0ab4710 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -19,7 +19,7 @@ import { rightShift64, leftShift32, } from './bitwise.js'; -import { Field } from '../core.js'; +import { Field } from '../field.js'; import { ForeignField, Field3, Sum } from './foreign-field.js'; import { divMod32, addMod32 } from './arithmetic.js'; From a1adbf6bf61169e4760083b651610e05f42c7090 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 1 Dec 2023 15:59:24 +0100 Subject: [PATCH 5/9] double --- src/examples/crypto/twisted-edwards.ts | 56 ++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/examples/crypto/twisted-edwards.ts b/src/examples/crypto/twisted-edwards.ts index 119ca0e2de..446f11d15c 100644 --- a/src/examples/crypto/twisted-edwards.ts +++ b/src/examples/crypto/twisted-edwards.ts @@ -4,7 +4,7 @@ * https://en.wikipedia.org/wiki/Twisted_Edwards_curve * https://en.wikipedia.org/wiki/EdDSA#Ed25519 */ -import { Provable, Struct, createForeignField } from 'o1js'; +import { Provable, Struct, createForeignField, Gadgets } from 'o1js'; const p = 2n ** 255n - 19n; const FpUnreduced = createForeignField(p); @@ -13,6 +13,8 @@ class Fp extends FpUnreduced.AlmostReduced {} type Point = { x: Fp; y: Fp }; const Point = Struct({ x: Fp.provable, y: Fp.provable }); +const { ForeignField } = Gadgets; + /** Curve equation: * * -x^2 + y^2 = 1 + d * x^2 * y^2 @@ -50,10 +52,50 @@ function add(p: Point, q: Point): Point { return { x: x3r, y: y3r }; } -let cs = Provable.constraintSystem(() => { - let p = Provable.witness(Point, Point.empty); - let q = Provable.witness(Point, Point.empty); +function double(p: Point): Point { + let { x: x1, y: y1 } = p; + + // x3 = 2*x1*y1 / (y1^2 - x1^2) + // y3 = (y1^2 + x1^2) / (2 - (y1^2 - x1^2)) + let x1x1 = x1.mul(x1); + let y1y1 = y1.mul(y1); + let x1y1 = x1.mul(y1); + + // witness x3, y3 + let { x: x3, y: y3 } = Provable.witness(Point, () => { + let d = y1y1.sub(x1x1).assertAlmostReduced(); + let x3 = x1y1.add(x1x1).assertAlmostReduced().div(d); + let y3 = y1y1 + .add(x1x1) + .assertAlmostReduced() + .div(Fp.from(2n).sub(d).assertAlmostReduced()); + return { x: x3, y: y3 }; + }); + + // TODO expose assertMul and Sum on the ForeignField class to make this nicer + + // x3*(y1^2 - x1^2) = x1*y1 + x1*y1 + ForeignField.assertMul( + x3.value, + ForeignField.Sum(y1y1.value).sub(x1x1.value), + ForeignField.Sum(x1y1.value).add(x1y1.value), + Fp.modulus + ); - let r = add(p, q); -}); -console.log(cs); + // y3*(2 - (y1^2 - x1^2)) = y1^2 + x1^2 + ForeignField.assertMul( + y3.value, + ForeignField.Sum(Fp.from(2n).value).sub(y1y1.value).add(x1x1.value), + ForeignField.Sum(y1y1.value).add(x1x1.value), + Fp.modulus + ); + + return { x: x3, y: y3 }; +} + +Provable.constraintSystem(() => add(dummy(), dummy())).print(); +Provable.constraintSystem(() => double(dummy())).print(); + +function dummy() { + return Provable.witness(Point, Point.empty); +} From 0604f1edbb82c57d51d2d53c635fc24d16e62faf Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 19 Dec 2023 16:26:13 +0100 Subject: [PATCH 6/9] bindigns --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 7af5696e11..f7ed6da631 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7af5696e110243b3a6e1760087384395042710d4 +Subproject commit f7ed6da631b2dbabccbc6e27e0e29af62505b9cc From 81e441c1b8da74600afbd6ab702920bece007142 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 26 Nov 2024 14:08:21 +0100 Subject: [PATCH 7/9] fix typo in doc comment --- src/examples/crypto/twisted-edwards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/crypto/twisted-edwards.ts b/src/examples/crypto/twisted-edwards.ts index 446f11d15c..c941c18e66 100644 --- a/src/examples/crypto/twisted-edwards.ts +++ b/src/examples/crypto/twisted-edwards.ts @@ -1,5 +1,5 @@ /** - * Implementation of twisted Edwards curves for Ed25529. + * Implementation of twisted Edwards curves for Ed25519. * * https://en.wikipedia.org/wiki/Twisted_Edwards_curve * https://en.wikipedia.org/wiki/EdDSA#Ed25519 From 537dccff83a938c33c1c1b503100c4490b55d996 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 26 Nov 2024 15:19:11 +0100 Subject: [PATCH 8/9] adapt format of constraint system --- src/examples/crypto/twisted-edwards.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/examples/crypto/twisted-edwards.ts b/src/examples/crypto/twisted-edwards.ts index c941c18e66..1204e6236e 100644 --- a/src/examples/crypto/twisted-edwards.ts +++ b/src/examples/crypto/twisted-edwards.ts @@ -93,8 +93,14 @@ function double(p: Point): Point { return { x: x3, y: y3 }; } -Provable.constraintSystem(() => add(dummy(), dummy())).print(); -Provable.constraintSystem(() => double(dummy())).print(); +{ + let { print } = await Provable.constraintSystem(() => add(dummy(), dummy())); + print(); +} +{ + let { print } = await Provable.constraintSystem(() => double(dummy())); + print(); +} function dummy() { return Provable.witness(Point, Point.empty); From a910d2c614c704b289c97ca5017d4b93fe49a577 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Dec 2024 15:13:53 +0100 Subject: [PATCH 9/9] update changelog --- CHANGELOG.md | 4 ++++ flake.lock | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da97589a32..c87fce55f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906 +### Added + +- Twisted Edwards curve operations https://github.com/o1-labs/o1js/pull/1283 + ## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13 ### Added diff --git a/flake.lock b/flake.lock index ee20e9300b..9aaa016eb8 100644 --- a/flake.lock +++ b/flake.lock @@ -265,7 +265,7 @@ "utils": "utils" }, "locked": { - "lastModified": 1732554073, + "lastModified": 1733157072, "narHash": "sha256-SaQSHg6hd7tSAU0MlIU1AMPTEpZHXldihr2PzZPcN3Q=", "path": "src/mina", "type": "path"