o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
507 lines • 16.6 kB
JavaScript
import { bigIntToBits } from './bigint-helpers.js';
import { Fp, createField, inverse, mod, p, q } from './finite-field.js';
import { Endomorphism } from './elliptic-curve-endomorphism.js';
export { Pallas, PallasAffine, Vesta, GroupMapPallas, createCurveProjective, createCurveAffine, affineAdd, affineDouble, affineScale, projectiveFromAffine, projectiveToAffine, projectiveZero, projectiveAdd, getProjectiveDouble, projectiveNeg, };
// TODO: constants, like generator points and cube roots for endomorphisms, should be drawn from
// a common source, i.e. generated from the Rust code
const pallasGeneratorProjective = {
x: 1n,
y: 12418654782883325593414442427049395787963493412651469444558597405572177144507n,
};
const vestaGeneratorProjective = {
x: 1n,
y: 11426906929455361843568202299992114520848200991084027513389447476559454104162n,
};
const vestaEndoBase = 2942865608506852014473558576493638302197734138389222805617480874486368177743n;
const pallasEndoBase = 20444556541222657078399132219657928148671392403212669005631716460534733845831n;
const vestaEndoScalar = 8503465768106391777493614032514048814691664078728891710322960303815233784505n;
const pallasEndoScalar = 26005156700822196841419187675678338661165322343552424574062261873906994770353n;
// the b and a in y^2 = x^3 + ax + b
const b = 5n;
const a = 0n;
const projectiveZero = { x: 1n, y: 1n, z: 0n };
// reference implementation https://github.com/o1-labs/snarky/blob/78e0d952518f75b5382f6d735adb24eef7a0fa90/group_map/group_map.ml
const GroupMap = {
create: (F, params) => {
const { a, b } = params.spec;
if (a !== 0n)
throw Error('GroupMap only supports a = 0');
function tryDecode(x) {
// x^3
const pow3 = F.power(x, 3n);
// a * x - since a = 0, ax will be 0 as well
// const ax = F.mul(a, x);
// x^3 + ax + b, but since ax = 0 we can write x^3 + b
const y = F.add(pow3, b);
if (!F.isSquare(y))
return undefined;
return { x, y: F.sqrt(y) };
}
function sToVTruncated(s) {
const { u, v, y } = s;
return [v, F.negate(F.add(u, v)), F.add(u, F.square(y))];
}
function conic_to_s(c) {
const d = F.div(c.z, c.y);
if (d === undefined)
throw Error(`Division undefined! ${c.z}/${c.y}`);
const v = F.sub(d, params.u_over_2);
return { u: params.u, v, y: c.y };
}
function field_to_conic(t) {
const { z: z0, y: y0 } = params.projection_point;
const ct = F.mul(params.conic_c, t);
const d1 = F.add(F.mul(ct, y0), z0);
const d2 = F.add(F.mul(ct, t), 1n);
const d = F.div(d1, d2);
if (d === undefined)
throw Error(`Division undefined! ${d1}/${d2}`);
const s = F.mul(2n, d);
return {
z: F.sub(z0, s),
y: F.sub(y0, F.mul(s, t)),
};
}
return {
potentialXs: (t) => sToVTruncated(conic_to_s(field_to_conic(t))),
tryDecode,
};
},
};
// https://github.com/MinaProtocol/mina/blob/af7bc89270b66c06e2cc8d1bb093ba31d6a7b372/src/lib/crypto_params/gen/gen.ml#L8-L11
const GroupMapParamsFp = {
u: 2n,
u_over_2: 1n,
conic_c: 3n,
projection_point: {
z: 12196889842669319921865617096620076994180062626450149327690483414064673774441n,
y: 1n,
},
spec: {
a: 0n,
b: 5n,
},
};
const GroupMapPallas = GroupMap.create(Fp, GroupMapParamsFp);
function projectiveNeg({ x, y, z }, p) {
return { x, y: y === 0n ? 0n : p - y, z };
}
function projectiveAdd(g, h, p, a) {
if (g.z === 0n)
return h;
if (h.z === 0n)
return g;
let X1 = g.x, Y1 = g.y, Z1 = g.z, X2 = h.x, Y2 = h.y, Z2 = h.z;
// http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
// Z1Z1 = Z1^2
let Z1Z1 = mod(Z1 * Z1, p);
// Z2Z2 = Z2^2
let Z2Z2 = mod(Z2 * Z2, p);
// U1 = X1*Z2Z2
let U1 = mod(X1 * Z2Z2, p);
// U2 = X2*Z1Z1
let U2 = mod(X2 * Z1Z1, p);
// S1 = Y1*Z2*Z2Z2
let S1 = mod(Y1 * Z2 * Z2Z2, p);
// S2 = Y2*Z1*Z1Z1
let S2 = mod(Y2 * Z1 * Z1Z1, p);
// H = U2-U1
let H = mod(U2 - U1, p);
// H = 0 <==> x1 = X1/Z1^2 = X2/Z2^2 = x2 <==> degenerate case (Z3 would become 0)
if (H === 0n) {
// if S1 = S2 <==> y1 = y2, the points are equal, so we double instead
if (S1 === S2)
return projectiveDouble(g, p, a);
// if S1 = -S2, the points are inverse, so return zero
if (mod(S1 + S2, p) === 0n)
return projectiveZero;
throw Error('projectiveAdd: invalid point');
}
// I = (2*H)^2
let I = mod((H * H) << 2n, p);
// J = H*I
let J = mod(H * I, p);
// r = 2*(S2-S1)
let r = 2n * (S2 - S1);
// V = U1*I
let V = mod(U1 * I, p);
// X3 = r^2-J-2*V
let X3 = mod(r * r - J - 2n * V, p);
// Y3 = r*(V-X3)-2*S1*J
let Y3 = mod(r * (V - X3) - 2n * S1 * J, p);
// Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2)*H
let Z3 = mod(((Z1 + Z2) * (Z1 + Z2) - Z1Z1 - Z2Z2) * H, p);
return { x: X3, y: Y3, z: Z3 };
}
/**
* Projective doubling in Jacobian coordinates, specialized to a=0
*
* Cost: 2M + 5S
*/
function projectiveDoubleA0(g, p) {
if (g.z === 0n)
return g;
let X1 = g.x, Y1 = g.y, Z1 = g.z;
if (Y1 === 0n)
throw Error('projectiveDouble: unhandled case');
// http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
// A = X1^2
let A = mod(X1 * X1, p);
// B = Y1^2
let B = mod(Y1 * Y1, p);
// C = B^2
let C = mod(B * B, p);
// D = 2*((X1+B)^2-A-C)
let D = mod(2n * ((X1 + B) * (X1 + B) - A - C), p);
// E = 3*A
let E = 3n * A;
// F = E^2
let F = mod(E * E, p);
// X3 = F-2*D
let X3 = mod(F - 2n * D, p);
// Y3 = E*(D-X3)-8*C
let Y3 = mod(E * (D - X3) - 8n * C, p);
// Z3 = 2*Y1*Z1
let Z3 = mod(2n * Y1 * Z1, p);
return { x: X3, y: Y3, z: Z3 };
}
/**
* Projective doubling in Jacobian coordinates, specialized to a=-3
*
* Cost: 3M + 5S
*/
function projectiveDoubleAminus3(g, p) {
if (g.z === 0n)
return g;
let X1 = g.x, Y1 = g.y, Z1 = g.z;
if (Y1 === 0n)
throw Error('projectiveDouble: unhandled case');
// http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
// delta = Z1^2
let delta = mod(Z1 * Z1, p);
// gamma = Y1^2
let gamma = mod(Y1 * Y1, p);
// beta = X1*gamma
let beta = mod(X1 * gamma, p);
// alpha = 3*(X1-delta)*(X1+delta)
let alpha = mod((X1 - delta) * (X1 + delta), p);
alpha = alpha + alpha + alpha;
// X3 = alpha^2-8*beta
let X3 = mod(alpha * alpha - 8n * beta, p);
// Z3 = (Y1+Z1)^2-gamma-delta
let Z3 = mod((Y1 + Z1) * (Y1 + Z1) - gamma - delta, p);
// Y3 = alpha*(4*beta-X3)-8*gamma^2
let Y3 = mod(alpha * (4n * beta - X3) - 8n * gamma * gamma, p);
return { x: X3, y: Y3, z: Z3 };
}
function projectiveDouble(g, p, a) {
if (a === 0n)
return projectiveDoubleA0(g, p);
if (a + 3n === p)
return projectiveDoubleAminus3(g, p);
throw Error('Projective doubling is not implemented for general curve parameter a, only a = 0 and a = -3');
}
function getProjectiveDouble(p, a) {
if (a === 0n)
return projectiveDoubleA0;
if (a + 3n === p)
return projectiveDoubleAminus3;
throw Error('Projective doubling is not implemented for general curve parameter a, only a = 0 and a = -3');
}
function projectiveSub(g, h, p, a) {
return projectiveAdd(g, projectiveNeg(h, p), p, a);
}
function projectiveScale(g, x, p, a) {
let double = getProjectiveDouble(p, a);
let bits = typeof x === 'bigint' ? bigIntToBits(x) : x;
let h = projectiveZero;
for (let bit of bits) {
if (bit)
h = projectiveAdd(h, g, p, a);
g = double(g, p);
}
return h;
}
function projectiveFromAffine({ x, y, infinity }) {
if (infinity)
return projectiveZero;
return { x, y, z: 1n };
}
function projectiveToAffine(g, p) {
let z = g.z;
if (z === 0n) {
// infinity
return { x: 0n, y: 0n, infinity: true };
}
else if (z === 1n) {
// already normalized affine form
return { x: g.x, y: g.y, infinity: false };
}
else {
let zinv = inverse(z, p); // we checked for z === 0, so inverse exists
let zinv_squared = mod(zinv * zinv, p);
// x/z^2
let x = mod(g.x * zinv_squared, p);
// y/z^3
let y = mod(g.y * zinv * zinv_squared, p);
return { x: x, y: y, infinity: false };
}
}
function projectiveEqual(g, h, p) {
// special case: z=0 can only be equal to another z=0; protects against (0,0,0) being equal to any point
if ((g.z === 0n || h.z === 0n) && g.z !== h.z)
return false;
// multiply out with z^2, z^3
let gz2 = mod(g.z * g.z, p);
let hz2 = mod(h.z * h.z, p);
// early return if gx !== hx
if (mod(g.x * hz2 - h.x * gz2, p) !== 0n)
return false;
let gz3 = mod(gz2 * g.z, p);
let hz3 = mod(hz2 * h.z, p);
return mod(g.y * hz3, p) === mod(h.y * gz3, p);
}
function projectiveOnCurve({ x, y, z }, p, b, a) {
// substitution x -> x/z^2 and y -> y/z^3 gives
// the equation y^2 = x^3 + a*x*z^4 + b*z^6
// (note: we allow a restricted set of x,y for z==0; this seems fine)
let x3 = mod(mod(x * x, p) * x, p);
let y2 = mod(y * y, p);
let z2 = mod(z * z, p);
let z4 = mod(z2 * z2, p);
let z6 = mod(z4 * z2, p);
return mod(y2 - x3 - a * x * z4 - b * z6, p) === 0n;
}
// checks whether the elliptic curve point g is in the subgroup defined by [order]g = 0
function projectiveInSubgroup(g, p, order, a) {
let orderTimesG = projectiveScale(g, order, p, a);
return projectiveEqual(orderTimesG, projectiveZero, p);
}
/**
* Projective curve arithmetic in Jacobian coordinates
*/
function createCurveProjective({ name, modulus: p, order, cofactor, generator, b, a, endoBase, endoScalar, }) {
let double = getProjectiveDouble(p, a);
cofactor ??= 1n;
let hasCofactor = cofactor !== 1n;
return {
name,
modulus: p,
order,
cofactor,
zero: projectiveZero,
one: { ...generator, z: 1n },
hasEndomorphism: endoBase !== undefined && endoScalar !== undefined,
get endoBase() {
if (endoBase === undefined)
throw Error('`endoBase` for this curve was not provided.');
return endoBase;
},
get endoScalar() {
if (endoScalar === undefined)
throw Error('`endoScalar` for this curve was not provided.');
return endoScalar;
},
a,
b,
hasCofactor,
equal(g, h) {
return projectiveEqual(g, h, p);
},
isOnCurve(g) {
return projectiveOnCurve(g, p, b, a);
},
isInSubgroup(g) {
return projectiveInSubgroup(g, p, order, a);
},
add(g, h) {
return projectiveAdd(g, h, p, a);
},
double(g) {
return double(g, p);
},
negate(g) {
return projectiveNeg(g, p);
},
sub(g, h) {
return projectiveSub(g, h, p, a);
},
scale(g, s) {
return projectiveScale(g, s, p, a);
},
endomorphism({ x, y, z }) {
if (endoBase === undefined)
throw Error('endomorphism needs `endoBase` parameter.');
return { x: mod(endoBase * x, p), y, z };
},
toAffine(g) {
return projectiveToAffine(g, p);
},
fromAffine(a) {
return projectiveFromAffine(a);
},
};
}
const Pallas = createCurveProjective({
name: 'Pallas',
modulus: p,
order: q,
generator: pallasGeneratorProjective,
b,
a,
endoBase: pallasEndoBase,
endoScalar: pallasEndoScalar,
});
const Vesta = createCurveProjective({
name: 'Vesta',
modulus: q,
order: p,
generator: vestaGeneratorProjective,
b,
a,
endoBase: vestaEndoBase,
endoScalar: vestaEndoScalar,
});
const affineZero = { x: 0n, y: 0n, infinity: true };
function affineOnCurve({ x, y, infinity }, p, a, b) {
if (infinity)
return true;
// y^2 = x^3 + ax + b
let x2 = mod(x * x, p);
return mod(y * y - x * x2 - a * x - b, p) === 0n;
}
function affineAdd(g, h, p, a) {
if (g.infinity)
return h;
if (h.infinity)
return g;
let { x: x1, y: y1 } = g;
let { x: x2, y: y2 } = h;
if (x1 === x2) {
// g + g --> we double
if (y1 === y2)
return affineDouble(g, p, a);
// g - g --> return zero
return affineZero;
}
// m = (y2 - y1)/(x2 - x1)
let d = inverse(x2 - x1, p);
if (d === undefined)
throw Error('impossible');
let m = mod((y2 - y1) * d, p);
// x3 = m^2 - x1 - x2
let x3 = mod(m * m - x1 - x2, p);
// y3 = m*(x1 - x3) - y1
let y3 = mod(m * (x1 - x3) - y1, p);
return { x: x3, y: y3, infinity: false };
}
function affineDouble({ x, y, infinity }, p, a) {
if (infinity)
return affineZero;
// m = (3*x^2 + a) / 2y
let d = inverse(2n * y, p);
if (d === undefined)
throw Error('impossible');
let m = mod((3n * x * x + a) * d, p);
// x2 = m^2 - 2x
let x2 = mod(m * m - 2n * x, p);
// y2 = m*(x - x2) - y
let y2 = mod(m * (x - x2) - y, p);
return { x: x2, y: y2, infinity: false };
}
function affineNegate({ x, y, infinity }, p) {
if (infinity)
return affineZero;
return { x, y: y === 0n ? 0n : p - y, infinity };
}
function affineScale(g, s, p, a) {
let gProj = projectiveFromAffine(g);
let sgProj = projectiveScale(gProj, s, p, a);
return projectiveToAffine(sgProj, p);
}
const PallasAffine = createCurveAffine({
name: 'Pallas',
modulus: p,
order: q,
generator: pallasGeneratorProjective,
b,
a,
endoBase: pallasEndoBase,
endoScalar: pallasEndoScalar,
});
function createCurveAffine({ name, modulus: p, order, cofactor, generator, a, b, endoScalar, endoBase, }) {
let hasCofactor = cofactor !== undefined && cofactor !== 1n;
const Field = createField(p);
const Scalar = createField(order);
const one = { ...generator, infinity: false };
const Endo = Endomorphism(Field, Scalar, one, a, endoScalar, endoBase);
return {
name,
/**
* Arithmetic over the base field
*/
Field,
/**
* Arithmetic over the scalar field
*/
Scalar,
modulus: p,
order,
a,
b,
cofactor,
hasCofactor,
zero: affineZero,
one,
hasEndomorphism: Endo !== undefined,
get Endo() {
if (Endo === undefined)
throw Error(`no endomorphism defined on ${name}`);
return Endo;
},
from(g) {
if (g.x === 0n && g.y === 0n)
return affineZero;
return { ...g, infinity: false };
},
fromNonzero(g) {
if (g.x === 0n && g.y === 0n) {
throw Error('fromNonzero: got (0, 0), which is reserved for the zero point');
}
return { ...g, infinity: false };
},
equal(g, h) {
if (g.infinity && h.infinity) {
return true;
}
else if (g.infinity || h.infinity) {
return false;
}
else {
return mod(g.x - h.x, p) === 0n && mod(g.y - h.y, p) === 0n;
}
},
isOnCurve(g) {
return affineOnCurve(g, p, a, b);
},
isInSubgroup(g) {
return projectiveInSubgroup(projectiveFromAffine(g), p, order, a);
},
add(g, h) {
return affineAdd(g, h, p, a);
},
double(g) {
return affineDouble(g, p, a);
},
negate(g) {
return affineNegate(g, p);
},
sub(g, h) {
return affineAdd(g, affineNegate(h, p), p, a);
},
scale(g, s) {
return affineScale(g, s, p, a);
},
};
}
//# sourceMappingURL=elliptic-curve.js.map