Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Twisted edwards curve operations #1283

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions src/examples/crypto/twisted-edwards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Implementation of twisted Edwards curves for Ed25519.
*
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve
* https://en.wikipedia.org/wiki/EdDSA#Ed25519
*/
import { Provable, Struct, createForeignField, Gadgets } 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 });

const { ForeignField } = Gadgets;

/** 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 };
}

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
);

// 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 };
}

{
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);
}
8 changes: 4 additions & 4 deletions src/lib/provable/gadgets/gadgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ const Gadgets = {
* Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND).
* The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise.
*
* It can be checked by a double generic gate that verifies the following relationship between the values
* It can be checked by a double generic gate that verifies the following relationship between the values
* below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`).
*
* The generic gate verifies:\
Expand All @@ -452,7 +452,7 @@ const Gadgets = {
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and)
*
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well.
*
* **Note:** Specifying a larger `length` parameter adds additional constraints.
Expand All @@ -476,8 +476,8 @@ const Gadgets = {
* Bitwise OR gadget on {@link Field} elements. Equivalent to the [bitwise OR `|` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR).
* The OR gate works by comparing two bits and returning `1` if at least one bit is `1`, and `0` otherwise.
*
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well.
*
* **Note:** Specifying a larger `length` parameter adds additional constraints.
Expand Down
Loading