UNPKG

@kotevode/ffjavascript

Version:

Finite Field Library in Javascript

438 lines (340 loc) 11.9 kB
/* 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; } } }