UNPKG

@aguycalled/noble-bls12-381

Version:

Fastest JS implementation of BLS12-381. Auditable, secure, 0-dependency aggregated signatures & pairings

1,160 lines (1,159 loc) 39.2 kB
export const CURVE = { P: 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn, r: 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n, h: 0x396c8c005555e1568c00aaab0000aaabn, Gx: 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn, Gy: 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n, b: 4n, P2: BigInt("0x2a437a4b8c35fc74bd278eaa22f25e9e2dc90e50e7046b466e59e49349e8bd050a62cfd16ddca6ef53149330978ef011d68619c86185c7b292e85a87091a04966bf91ed3e71b743162c338362113cfd7ced6b1d76382eab26aa00001c718e38"), h2: 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5n, G2x: [ 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, 0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7en, ], G2y: [ 0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801n, 0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79ben, ], b2: [4n, 4n], x: 0xd201000000010000n, h2Eff: 0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551n, }; const BLS_X_LEN = bitLen(CURVE.x); export function mod(a, b) { const res = a % b; return res >= 0n ? res : b + res; } export function powMod(a, power, modulo) { let res = 1n; while (power > 0n) { if (power & 1n) res = (res * a) % modulo; a = (a * a) % modulo; power >>= 1n; } return res; } function genInvertBatch(cls, nums) { const len = nums.length; const scratch = new Array(len); let acc = cls.ONE; for (let i = 0; i < len; i++) { if (nums[i].isZero()) continue; scratch[i] = acc; acc = acc.multiply(nums[i]); } acc = acc.invert(); for (let i = len - 1; i >= 0; i--) { if (nums[i].isZero()) continue; let tmp = acc.multiply(nums[i]); nums[i] = acc.multiply(scratch[i]); acc = tmp; } return nums; } function bitLen(n) { n = BigInt(n); let len; for (len = 0; n > 0n; n >>= BigInt(1), len += 1) ; return len; } function bitGet(n, pos) { return (n >> BigInt(pos)) & 1n; } function invert(number, modulo = CURVE.P) { const _0n = 0n, _1n = 1n; if (number === _0n || modulo <= _0n) { throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); } let a = mod(number, modulo); let b = modulo; let x = _0n, y = _1n, u = _1n, v = _0n; while (a !== _0n) { const q = b / a; const r = b % a; const m = x - u * q; const n = y - v * q; b = a, a = r, x = u, y = v, u = m, v = n; } const gcd = b; if (gcd !== _1n) throw new Error('invert: does not exist'); return mod(x, modulo); } export class Fp { constructor(value) { this.value = mod(value, Fp.ORDER); } isZero() { return this.value === 0n; } equals(rhs) { return this.value === rhs.value; } negate() { return new Fp(-this.value); } invert() { return new Fp(invert(this.value, Fp.ORDER)); } add(rhs) { return new Fp(this.value + rhs.value); } square() { return new Fp(this.value * this.value); } pow(n) { return new Fp(powMod(this.value, n, Fp.ORDER)); } sqrt() { const root = this.pow((Fp.ORDER + 1n) / 4n); if (!root.square().equals(this)) return; return root; } subtract(rhs) { return new Fp(this.value - rhs.value); } multiply(rhs) { if (rhs instanceof Fp) rhs = rhs.value; return new Fp(this.value * rhs); } div(rhs) { if (typeof rhs === 'bigint') rhs = new Fp(rhs); return this.multiply(rhs.invert()); } toString() { const str = this.value.toString(16).padStart(96, '0'); return str.slice(0, 2) + '.' + str.slice(-2); } } Fp.ORDER = CURVE.P; Fp.MAX_BITS = bitLen(CURVE.P); Fp.ZERO = new Fp(0n); Fp.ONE = new Fp(1n); export class Fr { constructor(value) { this.value = mod(value, Fr.ORDER); } static isValid(b) { return b <= Fr.ORDER; } isZero() { return this.value === 0n; } equals(rhs) { return this.value === rhs.value; } negate() { return new Fr(-this.value); } invert() { return new Fr(invert(this.value, Fr.ORDER)); } add(rhs) { return new Fr(this.value + rhs.value); } square() { return new Fr(this.value * this.value); } pow(n) { return new Fr(powMod(this.value, n, Fr.ORDER)); } subtract(rhs) { return new Fr(this.value - rhs.value); } multiply(rhs) { if (rhs instanceof Fr) rhs = rhs.value; return new Fr(this.value * rhs); } div(rhs) { if (typeof rhs === 'bigint') rhs = new Fr(rhs); return this.multiply(rhs.invert()); } legendre() { return this.pow((Fr.ORDER - 1n) / 2n); } sqrt() { if (!this.legendre().equals(Fr.ONE)) return; const P = Fr.ORDER; let q, s, z; for (q = P - 1n, s = 0; q % 2n === 0n; q /= 2n, s++) ; if (s === 1) return this.pow((P + 1n) / 4n); for (z = 2n; z < P && new Fr(z).legendre().value !== P - 1n; z++) ; let c = powMod(z, q, P); let r = powMod(this.value, (q + 1n) / 2n, P); let t = powMod(this.value, q, P); let t2 = 0n; while (mod(t - 1n, P) !== 0n) { t2 = mod(t * t, P); let i; for (i = 1; i < s; i++) { if (mod(t2 - 1n, P) === 0n) break; t2 = mod(t2 * t2, P); } let b = powMod(c, BigInt(1 << (s - i - 1)), P); r = mod(r * b, P); c = mod(b * b, P); t = mod(t * c, P); s = i; } return new Fr(r); } toString() { return '0x' + this.value.toString(16).padStart(64, '0'); } } Fr.ORDER = CURVE.r; Fr.ZERO = new Fr(0n); Fr.ONE = new Fr(1n); class FQP { zip(rhs, mapper) { const c0 = this.c; const c1 = rhs.c; const res = []; for (let i = 0; i < c0.length; i++) { res.push(mapper(c0[i], c1[i])); } return res; } map(callbackfn) { return this.c.map(callbackfn); } isZero() { return this.c.every((c) => c.isZero()); } equals(rhs) { return this.zip(rhs, (left, right) => left.equals(right)).every((r) => r); } negate() { return this.init(this.map((c) => c.negate())); } add(rhs) { return this.init(this.zip(rhs, (left, right) => left.add(right))); } subtract(rhs) { return this.init(this.zip(rhs, (left, right) => left.subtract(right))); } conjugate() { return this.init([this.c[0], this.c[1].negate()]); } one() { const el = this; let one; if (el instanceof Fp2) one = Fp2.ONE; if (el instanceof Fp6) one = Fp6.ONE; if (el instanceof Fp12) one = Fp12.ONE; return one; } pow(n) { const elm = this; const one = this.one(); if (n === 0n) return one; if (n === 1n) return elm; let p = one; let d = elm; while (n > 0n) { if (n & 1n) p = p.multiply(d); n >>= 1n; d = d.square(); } return p; } div(rhs) { const inv = typeof rhs === 'bigint' ? new Fp(rhs).invert().value : rhs.invert(); return this.multiply(inv); } } export class Fp2 extends FQP { constructor(coeffs) { super(); if (coeffs.length !== 2) throw new Error(`Expected array with 2 elements`); coeffs.forEach((c, i) => { if (typeof c === 'bigint') coeffs[i] = new Fp(c); }); this.c = coeffs; } init(tuple) { return new Fp2(tuple); } toString() { return `Fp2(${this.c[0]} + ${this.c[1]}×i)`; } get values() { return this.c.map((c) => c.value); } multiply(rhs) { if (typeof rhs === 'bigint') return new Fp2(this.map((c) => c.multiply(rhs))); const c0 = this.c[0]; const c1 = this.c[1]; const r0 = rhs.c[0]; const r1 = rhs.c[1]; let t1 = c0.multiply(r0); let t2 = c1.multiply(r1); return new Fp2([t1.subtract(t2), c0.add(c1).multiply(r0.add(r1)).subtract(t1.add(t2))]); } mulByNonresidue() { const c0 = this.c[0]; const c1 = this.c[1]; return new Fp2([c0.subtract(c1), c0.add(c1)]); } square() { const c0 = this.c[0]; const c1 = this.c[1]; const a = c0.add(c1); const b = c0.subtract(c1); const c = c0.add(c0); return new Fp2([a.multiply(b), c.multiply(c1)]); } sqrt() { const candidateSqrt = this.pow((Fp2.ORDER + 8n) / 16n); const check = candidateSqrt.square().div(this); const R = FP2_ROOTS_OF_UNITY; const divisor = [R[0], R[2], R[4], R[6]].find((r) => r.equals(check)); if (!divisor) return; const index = R.indexOf(divisor); const root = R[index / 2]; if (!root) throw new Error('Invalid root'); const x1 = candidateSqrt.div(root); const x2 = x1.negate(); const [re1, im1] = x1.values; const [re2, im2] = x2.values; if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1; return x2; } invert() { const ab = this.values; const a = ab[0]; const b = ab[1]; const factor = new Fp(a * a + b * b).invert(); return new Fp2([factor.multiply(new Fp(a)), factor.multiply(new Fp(-b))]); } frobeniusMap(power) { return new Fp2([this.c[0], this.c[1].multiply(FP2_FROBENIUS_COEFFICIENTS[power % 2])]); } multiplyByB() { let c0 = this.c[0]; let c1 = this.c[1]; let t0 = c0.multiply(4n); let t1 = c1.multiply(4n); return new Fp2([t0.subtract(t1), t0.add(t1)]); } } Fp2.ORDER = CURVE.P2; Fp2.MAX_BITS = bitLen(CURVE.P2); Fp2.ZERO = new Fp2([0n, 0n]); Fp2.ONE = new Fp2([1n, 0n]); export class Fp6 extends FQP { constructor(c) { super(); this.c = c; if (c.length !== 3) throw new Error(`Expected array with 3 elements`); } static fromTuple(t) { if (!Array.isArray(t) || t.length !== 6) throw new Error('Invalid Fp6 usage'); return new Fp6([new Fp2(t.slice(0, 2)), new Fp2(t.slice(2, 4)), new Fp2(t.slice(4, 6))]); } init(triple) { return new Fp6(triple); } toString() { return `Fp6(${this.c[0]} + ${this.c[1]} * v, ${this.c[2]} * v^2)`; } conjugate() { throw new TypeError('No conjugate on Fp6'); } multiply(rhs) { if (typeof rhs === 'bigint') return new Fp6([this.c[0].multiply(rhs), this.c[1].multiply(rhs), this.c[2].multiply(rhs)]); let c = this.c; let c0 = c[0]; let c1 = c[1]; let c2 = c[2]; const r = rhs.c; let r0 = r[0]; let r1 = r[1]; let r2 = r[2]; let t0 = c0.multiply(r0); let t1 = c1.multiply(r1); let t2 = c2.multiply(r2); return new Fp6([ t0.add(c1.add(c2).multiply(r1.add(r2)).subtract(t1.add(t2)).mulByNonresidue()), c0.add(c1).multiply(r0.add(r1)).subtract(t0.add(t1)).add(t2.mulByNonresidue()), t1.add(c0.add(c2).multiply(r0.add(r2)).subtract(t0.add(t2))), ]); } mulByNonresidue() { return new Fp6([this.c[2].mulByNonresidue(), this.c[0], this.c[1]]); } multiplyBy1(b1) { return new Fp6([ this.c[2].multiply(b1).mulByNonresidue(), this.c[0].multiply(b1), this.c[1].multiply(b1), ]); } multiplyBy01(b0, b1) { let c = this.c; let c0 = c[0]; let c1 = c[1]; let c2 = c[2]; let t0 = c0.multiply(b0); let t1 = c1.multiply(b1); return new Fp6([ c1.add(c2).multiply(b1).subtract(t1).mulByNonresidue().add(t0), b0.add(b1).multiply(c0.add(c1)).subtract(t0).subtract(t1), c0.add(c2).multiply(b0).subtract(t0).add(t1), ]); } multiplyByFp2(rhs) { return new Fp6(this.map((c) => c.multiply(rhs))); } square() { let c = this.c; let c0 = c[0]; let c1 = c[1]; let c2 = c[2]; let t0 = c0.square(); let t1 = c0.multiply(c1).multiply(2n); let t3 = c1.multiply(c2).multiply(2n); let t4 = c2.square(); return new Fp6([ t3.mulByNonresidue().add(t0), t4.mulByNonresidue().add(t1), t1.add(c0.subtract(c1).add(c2).square()).add(t3).subtract(t0).subtract(t4), ]); } invert() { let c = this.c; let c0 = c[0]; let c1 = c[1]; let c2 = c[2]; let t0 = c0.square().subtract(c2.multiply(c1).mulByNonresidue()); let t1 = c2.square().mulByNonresidue().subtract(c0.multiply(c1)); let t2 = c1.square().subtract(c0.multiply(c2)); let t4 = c2.multiply(t1).add(c1.multiply(t2)).mulByNonresidue().add(c0.multiply(t0)).invert(); return new Fp6([t4.multiply(t0), t4.multiply(t1), t4.multiply(t2)]); } frobeniusMap(power) { return new Fp6([ this.c[0].frobeniusMap(power), this.c[1].frobeniusMap(power).multiply(FP6_FROBENIUS_COEFFICIENTS_1[power % 6]), this.c[2].frobeniusMap(power).multiply(FP6_FROBENIUS_COEFFICIENTS_2[power % 6]), ]); } } Fp6.ZERO = new Fp6([Fp2.ZERO, Fp2.ZERO, Fp2.ZERO]); Fp6.ONE = new Fp6([Fp2.ONE, Fp2.ZERO, Fp2.ZERO]); export class Fp12 extends FQP { constructor(c) { super(); this.c = c; if (c.length !== 2) throw new Error(`Expected array with 2 elements`); } static fromTuple(t) { return new Fp12([ Fp6.fromTuple(t.slice(0, 6)), Fp6.fromTuple(t.slice(6, 12)), ]); } init(c) { return new Fp12(c); } toString() { return `Fp12(${this.c[0]} + ${this.c[1]} * w)`; } multiply(rhs) { if (typeof rhs === 'bigint') return new Fp12([this.c[0].multiply(rhs), this.c[1].multiply(rhs)]); let c = this.c; let c0 = c[0]; let c1 = c[1]; let r = rhs.c; let r0 = r[0]; let r1 = r[1]; let t1 = c0.multiply(r0); let t2 = c1.multiply(r1); return new Fp12([ t1.add(t2.mulByNonresidue()), c0.add(c1).multiply(r0.add(r1)).subtract(t1.add(t2)), ]); } multiplyBy014(o0, o1, o4) { let c = this.c; let c0 = c[0]; let c1 = c[1]; let t0 = c0.multiplyBy01(o0, o1); let t1 = c1.multiplyBy1(o4); return new Fp12([ t1.mulByNonresidue().add(t0), c1.add(c0).multiplyBy01(o0, o1.add(o4)).subtract(t0).subtract(t1), ]); } multiplyByFp2(rhs) { return this.init(this.map((c) => c.multiplyByFp2(rhs))); } square() { let c = this.c; let c0 = c[0]; let c1 = c[1]; let ab = c0.multiply(c1); return new Fp12([ c1.mulByNonresidue().add(c0).multiply(c0.add(c1)).subtract(ab).subtract(ab.mulByNonresidue()), ab.add(ab), ]); } invert() { let c = this.c; let c0 = c[0]; let c1 = c[1]; let t = c0.square().subtract(c1.square().mulByNonresidue()).invert(); return new Fp12([c0.multiply(t), c1.multiply(t).negate()]); } frobeniusMap(power) { let r0 = this.c[0].frobeniusMap(power); let c1 = this.c[1].frobeniusMap(power).c; const coeff = FP12_FROBENIUS_COEFFICIENTS[power % 12]; return new Fp12([ r0, new Fp6([c1[0].multiply(coeff), c1[1].multiply(coeff), c1[2].multiply(coeff)]), ]); } Fp4Square(a, b) { const a2 = a.square(); const b2 = b.square(); return [ b2.mulByNonresidue().add(a2), a.add(b).square().subtract(a2).subtract(b2), ]; } cyclotomicSquare() { const c0 = this.c[0]; const c1 = this.c[1]; const c0c0 = c0.c[0]; const c0c1 = c0.c[1]; const c0c2 = c0.c[2]; const c1c0 = c1.c[0]; const c1c1 = c1.c[1]; const c1c2 = c1.c[2]; const t3t4 = this.Fp4Square(c0c0, c1c1); const t5t6 = this.Fp4Square(c1c0, c0c2); const t7t8 = this.Fp4Square(c0c1, c1c2); let t3 = t3t4[0]; let t4 = t3t4[1]; let t5 = t5t6[0]; let t6 = t5t6[1]; let t7 = t7t8[0]; let t8 = t7t8[1]; let t9 = t8.mulByNonresidue(); return new Fp12([ new Fp6([ t3.subtract(c0c0).multiply(2n).add(t3), t5.subtract(c0c1).multiply(2n).add(t5), t7.subtract(c0c2).multiply(2n).add(t7), ]), new Fp6([ t9.add(c1c0).multiply(2n).add(t9), t4.add(c1c1).multiply(2n).add(t4), t6.add(c1c2).multiply(2n).add(t6), ]), ]); } cyclotomicExp(n) { let z = Fp12.ONE; for (let i = BLS_X_LEN - 1; i >= 0; i--) { z = z.cyclotomicSquare(); if (bitGet(n, i)) z = z.multiply(this); } return z; } finalExponentiate() { const { x } = CURVE; const t0 = this.frobeniusMap(6).div(this); const t1 = t0.frobeniusMap(2).multiply(t0); const t2 = t1.cyclotomicExp(x).conjugate(); const t3 = t1.cyclotomicSquare().conjugate().multiply(t2); const t4 = t3.cyclotomicExp(x).conjugate(); const t5 = t4.cyclotomicExp(x).conjugate(); const t6 = t5.cyclotomicExp(x).conjugate().multiply(t2.cyclotomicSquare()); const t7 = t6.cyclotomicExp(x).conjugate(); const t2_t5_pow_q2 = t2.multiply(t5).frobeniusMap(2); const t4_t1_pow_q3 = t4.multiply(t1).frobeniusMap(3); const t6_t1c_pow_q1 = t6.multiply(t1.conjugate()).frobeniusMap(1); const t7_t3c_t1 = t7.multiply(t3.conjugate()).multiply(t1); return t2_t5_pow_q2.multiply(t4_t1_pow_q3).multiply(t6_t1c_pow_q1).multiply(t7_t3c_t1); } } Fp12.ZERO = new Fp12([Fp6.ZERO, Fp6.ZERO]); Fp12.ONE = new Fp12([Fp6.ONE, Fp6.ZERO]); export class ProjectivePoint { constructor(x, y, z, C) { this.x = x; this.y = y; this.z = z; this.C = C; } isZero() { return this.z.isZero(); } createPoint(x, y, z) { return new this.constructor(x, y, z); } getZero() { return this.createPoint(this.C.ONE, this.C.ONE, this.C.ZERO); } equals(rhs) { if (this.constructor !== rhs.constructor) throw new Error(`ProjectivePoint#equals: this is ${this.constructor}, but rhs is ${rhs.constructor}`); const a = this; const b = rhs; const xe = a.x.multiply(b.z).equals(b.x.multiply(a.z)); const ye = a.y.multiply(b.z).equals(b.y.multiply(a.z)); return xe && ye; } negate() { return this.createPoint(this.x, this.y.negate(), this.z); } toString(isAffine = true) { if (!isAffine) { return `Point<x=${this.x}, y=${this.y}, z=${this.z}>`; } const [x, y] = this.toAffine(); return `Point<x=${x}, y=${y}>`; } fromAffineTuple(xy) { return this.createPoint(xy[0], xy[1], this.C.ONE); } toAffine(invZ = this.z.invert()) { return [this.x.multiply(invZ), this.y.multiply(invZ)]; } toAffineBatch(points) { const toInv = genInvertBatch(this.C, points.map((p) => p.z)); return points.map((p, i) => p.toAffine(toInv[i])); } normalizeZ(points) { return this.toAffineBatch(points).map((t) => this.fromAffineTuple(t)); } double() { const { x, y, z } = this; const W = x.multiply(x).multiply(3n); const S = y.multiply(z); const SS = S.multiply(S); const SSS = SS.multiply(S); const B = x.multiply(y).multiply(S); const H = W.multiply(W).subtract(B.multiply(8n)); const X3 = H.multiply(S).multiply(2n); const Y3 = W.multiply(B.multiply(4n).subtract(H)).subtract(y.multiply(y).multiply(8n).multiply(SS)); const Z3 = SSS.multiply(8n); return this.createPoint(X3, Y3, Z3); } add(rhs) { if (this.constructor !== rhs.constructor) throw new Error(`ProjectivePoint#add: this is ${this.constructor}, but rhs is ${rhs.constructor}`); const p1 = this; const p2 = rhs; if (p1.isZero()) return p2; if (p2.isZero()) return p1; const X1 = p1.x; const Y1 = p1.y; const Z1 = p1.z; const X2 = p2.x; const Y2 = p2.y; const Z2 = p2.z; const U1 = Y2.multiply(Z1); const U2 = Y1.multiply(Z2); const V1 = X2.multiply(Z1); const V2 = X1.multiply(Z2); if (V1.equals(V2) && U1.equals(U2)) return this.double(); if (V1.equals(V2)) return this.getZero(); const U = U1.subtract(U2); const V = V1.subtract(V2); const VV = V.multiply(V); const VVV = VV.multiply(V); const V2VV = V2.multiply(VV); const W = Z1.multiply(Z2); const A = U.multiply(U).multiply(W).subtract(VVV).subtract(V2VV.multiply(2n)); const X3 = V.multiply(A); const Y3 = U.multiply(V2VV.subtract(A)).subtract(VVV.multiply(U2)); const Z3 = VVV.multiply(W); return this.createPoint(X3, Y3, Z3); } subtract(rhs) { if (this.constructor !== rhs.constructor) throw new Error(`ProjectivePoint#subtract: this is ${this.constructor}, but rhs is ${rhs.constructor}`); return this.add(rhs.negate()); } validateScalar(n) { if (typeof n === 'number') n = BigInt(n); if (typeof n !== 'bigint' || n <= 0 || n > CURVE.r) { throw new Error(`Point#multiply: invalid scalar, expected positive integer < CURVE.r. Got: ${n}`); } return n; } multiplyUnsafe(scalar) { let n = this.validateScalar(scalar); let point = this.getZero(); let d = this; while (n > 0n) { if (n & 1n) point = point.add(d); d = d.double(); n >>= 1n; } return point; } multiply(scalar) { let n = this.validateScalar(scalar); let point = this.getZero(); let fake = this.getZero(); let d = this; let bits = Fp.ORDER; while (bits > 0n) { if (n & 1n) { point = point.add(d); } else { fake = fake.add(d); } d = d.double(); n >>= 1n; bits >>= 1n; } return point; } maxBits() { return this.C.MAX_BITS; } precomputeWindow(W) { const windows = Math.ceil(this.maxBits() / W); const windowSize = 2 ** (W - 1); let points = []; let p = this; let base = p; for (let window = 0; window < windows; window++) { base = p; points.push(base); for (let i = 1; i < windowSize; i++) { base = base.add(p); points.push(base); } p = base.double(); } return points; } calcMultiplyPrecomputes(W) { if (this._MPRECOMPUTES) throw new Error('This point already has precomputes'); this._MPRECOMPUTES = [W, this.normalizeZ(this.precomputeWindow(W))]; } clearMultiplyPrecomputes() { this._MPRECOMPUTES = undefined; } wNAF(n) { let W, precomputes; if (this._MPRECOMPUTES) { [W, precomputes] = this._MPRECOMPUTES; } else { W = 1; precomputes = this.precomputeWindow(W); } let p = this.getZero(); let f = this.getZero(); const windows = Math.ceil(this.maxBits() / W); const windowSize = 2 ** (W - 1); const mask = BigInt(2 ** W - 1); const maxNumber = 2 ** W; const shiftBy = BigInt(W); for (let window = 0; window < windows; window++) { const offset = window * windowSize; let wbits = Number(n & mask); n >>= shiftBy; if (wbits > windowSize) { wbits -= maxNumber; n += 1n; } if (wbits === 0) { f = f.add(window % 2 ? precomputes[offset].negate() : precomputes[offset]); } else { const cached = precomputes[offset + Math.abs(wbits) - 1]; p = p.add(wbits < 0 ? cached.negate() : cached); } } return [p, f]; } multiplyPrecomputed(scalar) { return this.wNAF(this.validateScalar(scalar))[0]; } } function sgn0(x) { const [x0, x1] = x.values; const sign_0 = x0 % 2n; const zero_0 = x0 === 0n; const sign_1 = x1 % 2n; return BigInt(sign_0 || (zero_0 && sign_1)); } const P_MINUS_9_DIV_16 = BigInt("0x2a437a4b8c35fc74bd278eaa22f25e9e2dc90e50e7046b466e59e49349e8bd050a62cfd16ddca6ef53149330978ef011d68619c86185c7b292e85a87091a04966bf91ed3e71b743162c338362113cfd7ced6b1d76382eab26aa00001c718e3"); function sqrt_div_fp2(u, v) { const v7 = v.pow(7n); const uv7 = u.multiply(v7); const uv15 = uv7.multiply(v7.multiply(v)); const gamma = uv15.pow(P_MINUS_9_DIV_16).multiply(uv7); let success = false; let result = gamma; const positiveRootsOfUnity = FP2_ROOTS_OF_UNITY.slice(0, 4); positiveRootsOfUnity.forEach((root) => { const candidate = root.multiply(gamma); if (candidate.pow(2n).multiply(v).subtract(u).isZero() && !success) { success = true; result = candidate; } }); return { success, sqrtCandidateOrGamma: result }; } export function map_to_curve_simple_swu_9mod16(t) { const iso_3_a = new Fp2([0n, 240n]); const iso_3_b = new Fp2([1012n, 1012n]); const iso_3_z = new Fp2([-2n, -1n]); if (Array.isArray(t)) t = new Fp2(t); const t2 = t.pow(2n); const iso_3_z_t2 = iso_3_z.multiply(t2); const ztzt = iso_3_z_t2.add(iso_3_z_t2.pow(2n)); let denominator = iso_3_a.multiply(ztzt).negate(); let numerator = iso_3_b.multiply(ztzt.add(Fp2.ONE)); if (denominator.isZero()) denominator = iso_3_z.multiply(iso_3_a); let v = denominator.pow(3n); let u = numerator .pow(3n) .add(iso_3_a.multiply(numerator).multiply(denominator.pow(2n))) .add(iso_3_b.multiply(v)); const { success, sqrtCandidateOrGamma } = sqrt_div_fp2(u, v); let y; if (success) y = sqrtCandidateOrGamma; const sqrtCandidateX1 = sqrtCandidateOrGamma.multiply(t.pow(3n)); u = iso_3_z_t2.pow(3n).multiply(u); let success2 = false; FP2_ETAs.forEach((eta) => { const etaSqrtCandidate = eta.multiply(sqrtCandidateX1); const temp = etaSqrtCandidate.pow(2n).multiply(v).subtract(u); if (temp.isZero() && !success && !success2) { y = etaSqrtCandidate; success2 = true; } }); if (!success && !success2) throw new Error('Hash to Curve - Optimized SWU failure'); if (success2) numerator = numerator.multiply(iso_3_z_t2); y = y; if (sgn0(t) !== sgn0(y)) y = y.negate(); y = y.multiply(denominator); return [numerator, y, denominator]; } export function isogenyMapG2(xyz) { const x = xyz[0], y = xyz[1], z = xyz[2]; const zz = z.multiply(z); const zzz = zz.multiply(z); const zPowers = [z, zz, zzz]; const mapped = [Fp2.ZERO, Fp2.ZERO, Fp2.ZERO, Fp2.ZERO]; for (let i = 0; i < ISOGENY_COEFFICIENTS.length; i++) { const k_i = ISOGENY_COEFFICIENTS[i]; mapped[i] = k_i.slice(-1)[0]; const arr = k_i.slice(0, -1).reverse(); for (let j = 0; j < arr.length; j++) { const k_i_j = arr[j]; mapped[i] = mapped[i].multiply(x).add(zPowers[j].multiply(k_i_j)); } } mapped[2] = mapped[2].multiply(y); mapped[3] = mapped[3].multiply(z); const z2 = mapped[1].multiply(mapped[3]); const x2 = mapped[0].multiply(mapped[3]); const y2 = mapped[1].multiply(mapped[2]); return [x2, y2, z2]; } export function calcPairingPrecomputes(x, y) { const Qx = x, Qy = y, Qz = Fp2.ONE; let Rx = Qx, Ry = Qy, Rz = Qz; let ell_coeff = []; for (let i = BLS_X_LEN - 2; i >= 0; i--) { let t0 = Ry.square(); let t1 = Rz.square(); let t2 = t1.multiply(3n).multiplyByB(); let t3 = t2.multiply(3n); let t4 = Ry.add(Rz).square().subtract(t1).subtract(t0); ell_coeff.push([ t2.subtract(t0), Rx.square().multiply(3n), t4.negate(), ]); Rx = t0.subtract(t3).multiply(Rx).multiply(Ry).div(2n); Ry = t0.add(t3).div(2n).square().subtract(t2.square().multiply(3n)); Rz = t0.multiply(t4); if (bitGet(CURVE.x, i)) { let t0 = Ry.subtract(Qy.multiply(Rz)); let t1 = Rx.subtract(Qx.multiply(Rz)); ell_coeff.push([ t0.multiply(Qx).subtract(t1.multiply(Qy)), t0.negate(), t1, ]); let t2 = t1.square(); let t3 = t2.multiply(t1); let t4 = t2.multiply(Rx); let t5 = t3.subtract(t4.multiply(2n)).add(t0.square().multiply(Rz)); Rx = t1.multiply(t5); Ry = t4.subtract(t5).multiply(t0).subtract(t3.multiply(Ry)); Rz = Rz.multiply(t3); } } return ell_coeff; } export function millerLoop(ell, g1) { const Px = g1[0].value; const Py = g1[1].value; let f12 = Fp12.ONE; for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) { const E = ell[j]; f12 = f12.multiplyBy014(E[0], E[1].multiply(Px), E[2].multiply(Py)); if (bitGet(CURVE.x, i)) { j += 1; const F = ell[j]; f12 = f12.multiplyBy014(F[0], F[1].multiply(Px), F[2].multiply(Py)); } if (i !== 0) f12 = f12.square(); } return f12.conjugate(); } const ut_root = new Fp6([Fp2.ZERO, Fp2.ONE, Fp2.ZERO]); const wsq = new Fp12([ut_root, Fp6.ZERO]); const wsq_inv = wsq.invert(); const wcu = new Fp12([Fp6.ZERO, ut_root]); const wcu_inv = wcu.invert(); export function psi(x, y) { const x2 = wsq_inv.multiplyByFp2(x).frobeniusMap(1).multiply(wsq).c[0].c[0]; const y2 = wcu_inv.multiplyByFp2(y).frobeniusMap(1).multiply(wcu).c[0].c[0]; return [x2, y2]; } export function psi2(x, y) { return [x.multiply(PSI2_C1), y.negate()]; } const PSI2_C1 = 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn; const rv1 = 0x6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n; const ev1 = 0x699be3b8c6870965e5bf892ad5d2cc7b0e85a117402dfd83b7f4a947e02d978498255a2aaec0ac627b5afbdf1bf1c90n; const ev2 = 0x8157cd83046453f5dd0972b6e3949e4288020b5b8a9cc99ca07e27089a2ce2436d965026adad3ef7baba37f2183e9b5n; const ev3 = 0xab1c2ffdd6c253ca155231eb3e71ba044fd562f6f72bc5bad5ec46a0b7a3b0247cf08ce6c6317f40edbc653a72dee17n; const ev4 = 0xaa404866706722864480885d68ad0ccac1967c7544b447873cc37e0181271e006df72162a3d3e0287bf597fbf7f8fc1n; const FP2_FROBENIUS_COEFFICIENTS = [ 0x1n, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, ].map((item) => new Fp(item)); const FP2_ROOTS_OF_UNITY = [ [1n, 0n], [rv1, -rv1], [0n, 1n], [rv1, rv1], [-1n, 0n], [-rv1, rv1], [0n, -1n], [-rv1, -rv1], ].map((pair) => new Fp2(pair)); const FP2_ETAs = [ [ev1, ev2], [-ev2, ev1], [ev3, ev4], [-ev4, ev3], ].map((pair) => new Fp2(pair)); const FP6_FROBENIUS_COEFFICIENTS_1 = [ [0x1n, 0x0n], [ 0x0n, 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, ], [ 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, 0x0n, ], [0x0n, 0x1n], [ 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, 0x0n, ], [ 0x0n, 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, ], ].map((pair) => new Fp2(pair)); const FP6_FROBENIUS_COEFFICIENTS_2 = [ [0x1n, 0x0n], [ 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaadn, 0x0n, ], [ 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, 0x0n, ], [ 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, 0x0n, ], [ 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, 0x0n, ], [ 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffffn, 0x0n, ], ].map((pair) => new Fp2(pair)); const FP12_FROBENIUS_COEFFICIENTS = [ [0x1n, 0x0n], [ 0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8n, 0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3n, ], [ 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffeffffn, 0x0n, ], [ 0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2n, 0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n, ], [ 0x00000000000000005f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffen, 0x0n, ], [ 0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995n, 0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116n, ], [ 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaan, 0x0n, ], [ 0x00fc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3n, 0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8n, ], [ 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaacn, 0x0n, ], [ 0x06af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09n, 0x135203e60180a68ee2e9c448d77a2cd91c3dedd930b1cf60ef396489f61eb45e304466cf3e67fa0af1ee7b04121bdea2n, ], [ 0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaadn, 0x0n, ], [ 0x05b2cfd9013a5fd8df47fa6b48b1e045f39816240c0b8fee8beadf4d8e9c0566c63a3e6e257f87329b18fae980078116n, 0x144e4211384586c16bd3ad4afa99cc9170df3560e77982d0db45f3536814f0bd5871c1908bd478cd1ee605167ff82995n, ], ].map((pair) => new Fp2(pair)); const xnum = [ [ 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6n, 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6n, ], [ 0x0n, 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71an, ], [ 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71en, 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38dn, ], [ 0x171d6541fa38ccfaed6dea691f5fb614cb14b4e7f4e810aa22d6108f142b85757098e38d0f671c7188e2aaaaaaaa5ed1n, 0x0n, ], ].map((pair) => new Fp2(pair)); const xden = [ [ 0x0n, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa63n, ], [ 0xcn, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa9fn, ], [0x1n, 0x0n], [0x0n, 0x0n], ].map((pair) => new Fp2(pair)); const ynum = [ [ 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706n, 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706n, ], [ 0x0n, 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97ben, ], [ 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71cn, 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38fn, ], [ 0x124c9ad43b6cf79bfbf7043de3811ad0761b0f37a1e26286b0e977c69aa274524e79097a56dc4bd9e1b371c71c718b10n, 0x0n, ], ].map((pair) => new Fp2(pair)); const yden = [ [ 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fbn, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fbn, ], [ 0x0n, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9d3n, ], [ 0x12n, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa99n, ], [0x1n, 0x0n], ].map((pair) => new Fp2(pair)); const ISOGENY_COEFFICIENTS = [xnum, xden, ynum, yden];