@kotevode/ffjavascript
Version:
Finite Field Library in Javascript
438 lines (340 loc) • 11.9 kB
JavaScript
/*
Copyright 2018 0kims association.
This file is part of snarkjs.
snarkjs is a free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your option)
any later version.
snarkjs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
snarkjs. If not, see <https://www.gnu.org/licenses/>.
*/
import * as fUtils from "./futils.js";
import * as Scalar from "./scalar.js";
function isGreatest(F, a) {
if (Array.isArray(a)) {
for (let i=a.length-1; i>=0; i--) {
if (!F.F.isZero(a[i])) {
return isGreatest(F.F, a[i]);
}
}
return 0;
} else {
const na = F.neg(a);
return Scalar.gt(a, na);
}
}
export default class EC {
constructor(F, g) {
this.F = F;
this.g = g;
if (this.g.length == 2) this.g[2] = this.F.one;
this.zero = [this.F.zero, this.F.one, this.F.zero];
}
add(p1, p2) {
const F = this.F;
if (this.eq(p1, this.zero)) return p2;
if (this.eq(p2, this.zero)) return p1;
const res = new Array(3);
const Z1Z1 = F.square( p1[2] );
const Z2Z2 = F.square( p2[2] );
const U1 = F.mul( p1[0] , Z2Z2 ); // U1 = X1 * Z2Z2
const U2 = F.mul( p2[0] , Z1Z1 ); // U2 = X2 * Z1Z1
const Z1_cubed = F.mul( p1[2] , Z1Z1);
const Z2_cubed = F.mul( p2[2] , Z2Z2);
const S1 = F.mul( p1[1] , Z2_cubed); // S1 = Y1 * Z2 * Z2Z2
const S2 = F.mul( p2[1] , Z1_cubed); // S2 = Y2 * Z1 * Z1Z1
if (F.eq(U1,U2) && F.eq(S1,S2)) {
return this.double(p1);
}
const H = F.sub( U2 , U1 ); // H = U2-U1
const S2_minus_S1 = F.sub( S2 , S1 );
const I = F.square( F.add(H,H) ); // I = (2 * H)^2
const J = F.mul( H , I ); // J = H * I
const r = F.add( S2_minus_S1 , S2_minus_S1 ); // r = 2 * (S2-S1)
const V = F.mul( U1 , I ); // V = U1 * I
res[0] =
F.sub(
F.sub( F.square(r) , J ),
F.add( V , V )); // X3 = r^2 - J - 2 * V
const S1_J = F.mul( S1 , J );
res[1] =
F.sub(
F.mul( r , F.sub(V,res[0])),
F.add( S1_J,S1_J )); // Y3 = r * (V-X3)-2 S1 J
res[2] =
F.mul(
H,
F.sub(
F.square( F.add(p1[2],p2[2]) ),
F.add( Z1Z1 , Z2Z2 ))); // Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2) * H
return res;
}
neg(p) {
return [p[0], this.F.neg(p[1]), p[2]];
}
sub(a, b) {
return this.add(a, this.neg(b));
}
double(p) {
const F = this.F;
const res = new Array(3);
if (this.eq(p, this.zero)) return p;
const A = F.square( p[0] ); // A = X1^2
const B = F.square( p[1] ); // B = Y1^2
const C = F.square( B ); // C = B^2
let D =
F.sub(
F.square( F.add(p[0] , B )),
F.add( A , C));
D = F.add(D,D); // D = 2 * ((X1 + B)^2 - A - C)
const E = F.add( F.add(A,A), A); // E = 3 * A
const FF =F.square( E ); // F = E^2
res[0] = F.sub( FF , F.add(D,D) ); // X3 = F - 2 D
let eightC = F.add( C , C );
eightC = F.add( eightC , eightC );
eightC = F.add( eightC , eightC );
res[1] =
F.sub(
F.mul(
E,
F.sub( D, res[0] )),
eightC); // Y3 = E * (D - X3) - 8 * C
const Y1Z1 = F.mul( p[1] , p[2] );
res[2] = F.add( Y1Z1 , Y1Z1 ); // Z3 = 2 * Y1 * Z1
return res;
}
timesScalar(base, e) {
return fUtils.mulScalar(this, base, e);
}
mulScalar(base, e) {
return fUtils.mulScalar(this, base, e);
}
affine(p) {
const F = this.F;
if (this.isZero(p)) {
return this.zero;
} else if (F.eq(p[2], F.one)) {
return p;
} else {
const Z_inv = F.inv(p[2]);
const Z2_inv = F.square(Z_inv);
const Z3_inv = F.mul(Z2_inv, Z_inv);
const res = new Array(3);
res[0] = F.mul(p[0],Z2_inv);
res[1] = F.mul(p[1],Z3_inv);
res[2] = F.one;
return res;
}
}
multiAffine(arr) {
const keys = Object.keys(arr);
const F = this.F;
const accMul = new Array(keys.length+1);
accMul[0] = F.one;
for (let i = 0; i< keys.length; i++) {
if (F.eq(arr[keys[i]][2], F.zero)) {
accMul[i+1] = accMul[i];
} else {
accMul[i+1] = F.mul(accMul[i], arr[keys[i]][2]);
}
}
accMul[keys.length] = F.inv(accMul[keys.length]);
for (let i = keys.length-1; i>=0; i--) {
if (F.eq(arr[keys[i]][2], F.zero)) {
accMul[i] = accMul[i+1];
arr[keys[i]] = this.zero;
} else {
const Z_inv = F.mul(accMul[i], accMul[i+1]);
accMul[i] = F.mul(arr[keys[i]][2], accMul[i+1]);
const Z2_inv = F.square(Z_inv);
const Z3_inv = F.mul(Z2_inv, Z_inv);
arr[keys[i]][0] = F.mul(arr[keys[i]][0],Z2_inv);
arr[keys[i]][1] = F.mul(arr[keys[i]][1],Z3_inv);
arr[keys[i]][2] = F.one;
}
}
}
eq(p1, p2) {
const F = this.F;
if (this.F.eq(p1[2], this.F.zero)) return this.F.eq(p2[2], this.F.zero);
if (this.F.eq(p2[2], this.F.zero)) return false;
const Z1Z1 = F.square( p1[2] );
const Z2Z2 = F.square( p2[2] );
const U1 = F.mul( p1[0] , Z2Z2 );
const U2 = F.mul( p2[0] , Z1Z1 );
const Z1_cubed = F.mul( p1[2] , Z1Z1);
const Z2_cubed = F.mul( p2[2] , Z2Z2);
const S1 = F.mul( p1[1] , Z2_cubed);
const S2 = F.mul( p2[1] , Z1_cubed);
return (F.eq(U1,U2) && F.eq(S1,S2));
}
isZero(p) {
return this.F.isZero(p[2]);
}
toString(p) {
const cp = this.affine(p);
return `[ ${this.F.toString(cp[0])} , ${this.F.toString(cp[1])} ]`;
}
fromRng(rng) {
const F = this.F;
let P = [];
let greatest;
do {
P[0] = F.fromRng(rng);
greatest = rng.nextBool();
const x3b = F.add(F.mul(F.square(P[0]), P[0]), this.b);
P[1] = F.sqrt(x3b);
} while ((P[1] == null)||(F.isZero[P]));
const s = isGreatest(F, P[1]);
if (greatest ^ s) P[1] = F.neg(P[1]);
P[2] = F.one;
if (this.cofactor) {
P = this.mulScalar(P, this.cofactor);
}
P = this.affine(P);
return P;
}
toRprLE(buff, o, p) {
p = this.affine(p);
if (this.isZero(p)) {
const BuffV = new Uint8Array(buff, o, this.F.n8*2);
BuffV.fill(0);
return;
}
this.F.toRprLE(buff, o, p[0]);
this.F.toRprLE(buff, o+this.F.n8, p[1]);
}
toRprBE(buff, o, p) {
p = this.affine(p);
if (this.isZero(p)) {
const BuffV = new Uint8Array(buff, o, this.F.n8*2);
BuffV.fill(0);
return;
}
this.F.toRprBE(buff, o, p[0]);
this.F.toRprBE(buff, o+this.F.n8, p[1]);
}
toRprLEM(buff, o, p) {
p = this.affine(p);
if (this.isZero(p)) {
const BuffV = new Uint8Array(buff, o, this.F.n8*2);
BuffV.fill(0);
return;
}
this.F.toRprLEM(buff, o, p[0]);
this.F.toRprLEM(buff, o+this.F.n8, p[1]);
}
toRprLEJM(buff, o, p) {
p = this.affine(p);
if (this.isZero(p)) {
const BuffV = new Uint8Array(buff, o, this.F.n8*2);
BuffV.fill(0);
return;
}
this.F.toRprLEM(buff, o, p[0]);
this.F.toRprLEM(buff, o+this.F.n8, p[1]);
this.F.toRprLEM(buff, o+2*this.F.n8, p[2]);
}
toRprBEM(buff, o, p) {
p = this.affine(p);
if (this.isZero(p)) {
const BuffV = new Uint8Array(buff, o, this.F.n8*2);
BuffV.fill(0);
return;
}
this.F.toRprBEM(buff, o, p[0]);
this.F.toRprBEM(buff, o+this.F.n8, p[1]);
}
fromRprLE(buff, o) {
o = o || 0;
const x = this.F.fromRprLE(buff, o);
const y = this.F.fromRprLE(buff, o+this.F.n8);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, this.F.one];
}
fromRprBE(buff, o) {
o = o || 0;
const x = this.F.fromRprBE(buff, o);
const y = this.F.fromRprBE(buff, o+this.F.n8);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, this.F.one];
}
fromRprLEM(buff, o) {
o = o || 0;
const x = this.F.fromRprLEM(buff, o);
const y = this.F.fromRprLEM(buff, o+this.F.n8);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, this.F.one];
}
fromRprLEJM(buff, o) {
o = o || 0;
const x = this.F.fromRprLEM(buff, o);
const y = this.F.fromRprLEM(buff, o+this.F.n8);
const z = this.F.fromRprLEM(buff, o+this.F.n8*2);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, z];
}
fromRprBEM(buff, o) {
o = o || 0;
const x = this.F.fromRprBEM(buff, o);
const y = this.F.fromRprBEM(buff, o+this.F.n8);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, this.F.one];
}
fromRprCompressed(buff, o) {
const F = this.F;
const v = new Uint8Array(buff.buffer, o, F.n8);
if (v[0] & 0x40) return this.zero;
const P = new Array(3);
const greatest = ((v[0] & 0x80) != 0);
v[0] = v[0] & 0x7F;
P[0] = F.fromRprBE(buff, o);
if (greatest) v[0] = v[0] | 0x80; // set back again the old value
const x3b = F.add(F.mul(F.square(P[0]), P[0]), this.b);
P[1] = F.sqrt(x3b);
if (P[1] === null) {
throw new Error("Invalid Point!");
}
const s = isGreatest(F, P[1]);
if (greatest ^ s) P[1] = F.neg(P[1]);
P[2] = F.one;
return P;
}
toRprCompressed(buff, o, p) {
p = this.affine(p);
const v = new Uint8Array(buff.buffer, o, this.F.n8);
if (this.isZero(p)) {
v.fill(0);
v[0] = 0x40;
return;
}
this.F.toRprBE(buff, o, p[0]);
if (isGreatest(this.F, p[1])) {
v[0] = v[0] | 0x80;
}
}
fromRprUncompressed(buff, o) {
if (buff[0] & 0x40) return this.zero;
return this.fromRprBE(buff, o);
}
toRprUncompressed(buff, o, p) {
this.toRprBE(buff, o, p);
if (this.isZero(p)) {
buff[o] = buff[o] | 0x40;
}
}
}