react-native-elliptic-curve-cryptography
Version:
[secp256k1](https://www.secg.org/sec2-v2.pdf), an elliptic curve that could be used for asymmetric encryption, ECDH key agreement protocol and deterministic ECDSA signature scheme from RFC6979.
692 lines (691 loc) • 24.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const CURVE = {
a: BigInt("0"),
b: BigInt("7"),
P: BigInt("2") ** BigInt("256") - BigInt("2") ** BigInt("32") - BigInt("977"),
n: BigInt("2") ** BigInt("256") - BigInt("432420386565659656852420866394968145599"),
h: BigInt("1"),
Gx: BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),
Gy: BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),
beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),
};
exports.CURVE = CURVE;
const PRIME_SIZE = 256;
const P_DIV4_1 = (CURVE.P + BigInt("1")) / BigInt("4");
function weistrass(x) {
const { a, b } = CURVE;
return mod(x ** BigInt("3") + a * x + b);
}
const USE_ENDOMORPHISM = CURVE.a === BigInt("0");
class JacobianPoint {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
static fromAffine(p) {
if (!(p instanceof Point)) {
throw new TypeError('JacobianPoint#fromAffine: expected Point');
}
return new JacobianPoint(p.x, p.y, BigInt("1"));
}
static toAffineBatch(points) {
const toInv = invertBatch(points.map((p) => p.z));
return points.map((p, i) => p.toAffine(toInv[i]));
}
static normalizeZ(points) {
return JacobianPoint.toAffineBatch(points).map(JacobianPoint.fromAffine);
}
equals(other) {
const a = this;
const b = other;
const az2 = mod(a.z * a.z);
const az3 = mod(a.z * az2);
const bz2 = mod(b.z * b.z);
const bz3 = mod(b.z * bz2);
return mod(a.x * bz2) === mod(az2 * b.x) && mod(a.y * bz3) === mod(az3 * b.y);
}
negate() {
return new JacobianPoint(this.x, mod(-this.y), this.z);
}
double() {
const X1 = this.x;
const Y1 = this.y;
const Z1 = this.z;
const A = X1 ** BigInt("2");
const B = Y1 ** BigInt("2");
const C = B ** BigInt("2");
const D = BigInt("2") * ((X1 + B) ** BigInt("2") - A - C);
const E = BigInt("3") * A;
const F = E ** BigInt("2");
const X3 = mod(F - BigInt("2") * D);
const Y3 = mod(E * (D - X3) - BigInt("8") * C);
const Z3 = mod(BigInt("2") * Y1 * Z1);
return new JacobianPoint(X3, Y3, Z3);
}
add(other) {
if (!(other instanceof JacobianPoint)) {
throw new TypeError('JacobianPoint#add: expected JacobianPoint');
}
const X1 = this.x;
const Y1 = this.y;
const Z1 = this.z;
const X2 = other.x;
const Y2 = other.y;
const Z2 = other.z;
if (X2 === BigInt("0") || Y2 === BigInt("0"))
return this;
if (X1 === BigInt("0") || Y1 === BigInt("0"))
return other;
const Z1Z1 = Z1 ** BigInt("2");
const Z2Z2 = Z2 ** BigInt("2");
const U1 = X1 * Z2Z2;
const U2 = X2 * Z1Z1;
const S1 = Y1 * Z2 * Z2Z2;
const S2 = Y2 * Z1 * Z1Z1;
const H = mod(U2 - U1);
const r = mod(S2 - S1);
if (H === BigInt("0")) {
if (r === BigInt("0")) {
return this.double();
}
else {
return JacobianPoint.ZERO;
}
}
const HH = mod(H ** BigInt("2"));
const HHH = mod(H * HH);
const V = U1 * HH;
const X3 = mod(r ** BigInt("2") - HHH - BigInt("2") * V);
const Y3 = mod(r * (V - X3) - S1 * HHH);
const Z3 = mod(Z1 * Z2 * H);
return new JacobianPoint(X3, Y3, Z3);
}
multiplyUnsafe(scalar) {
if (typeof scalar !== 'number' && typeof scalar !== 'bigint') {
throw new TypeError('Point#multiply: expected number or bigint');
}
let n = mod(BigInt(scalar), CURVE.n);
if (n <= 0) {
throw new Error('Point#multiply: invalid scalar, expected positive integer');
}
if (!USE_ENDOMORPHISM) {
let p = JacobianPoint.ZERO;
let d = this;
while (n > BigInt("0")) {
if (n & BigInt("1"))
p = p.add(d);
d = d.double();
n >>= BigInt("1");
}
return p;
}
let [k1neg, k1, k2neg, k2] = splitScalarEndo(n);
let k1p = JacobianPoint.ZERO;
let k2p = JacobianPoint.ZERO;
let d = this;
while (k1 > BigInt("0") || k2 > BigInt("0")) {
if (k1 & BigInt("1"))
k1p = k1p.add(d);
if (k2 & BigInt("1"))
k2p = k2p.add(d);
d = d.double();
k1 >>= BigInt("1");
k2 >>= BigInt("1");
}
if (k1neg)
k1p = k1p.negate();
if (k2neg)
k2p = k2p.negate();
k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z);
return k1p.add(k2p);
}
precomputeWindow(W) {
const windows = USE_ENDOMORPHISM ? 128 / W + 2 : 256 / 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 < 2 ** (W - 1); i++) {
base = base.add(p);
points.push(base);
}
p = base.double();
}
return points;
}
wNAF(n, affinePoint) {
if (!affinePoint && this.equals(JacobianPoint.BASE))
affinePoint = Point.BASE;
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
if (256 % W) {
throw new Error('Point#wNAF: Invalid precomputation window, must be power of 2');
}
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
if (!precomputes) {
precomputes = this.precomputeWindow(W);
if (affinePoint && W !== 1) {
precomputes = JacobianPoint.normalizeZ(precomputes);
pointPrecomputes.set(affinePoint, precomputes);
}
}
let p = JacobianPoint.ZERO;
let f = JacobianPoint.ZERO;
const windows = USE_ENDOMORPHISM ? 128 / W + 2 : 256 / W + 1;
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 += BigInt(1);
}
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];
}
multiply(scalar, affinePoint) {
if (typeof scalar !== 'number' && typeof scalar !== 'bigint') {
throw new TypeError('Point#multiply: expected number or bigint');
}
let n = mod(BigInt(scalar), CURVE.n);
if (n <= 0) {
throw new Error('Point#multiply: invalid scalar, expected positive integer');
}
let point;
let fake;
if (USE_ENDOMORPHISM) {
const [k1neg, k1, k2neg, k2] = splitScalarEndo(n);
let k1p, k2p, f1p, f2p;
[k1p, f1p] = this.wNAF(k1, affinePoint);
[k2p, f2p] = this.wNAF(k2, affinePoint);
if (k1neg)
k1p = k1p.negate();
if (k2neg)
k2p = k2p.negate();
k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z);
[point, fake] = [k1p.add(k2p), f1p.add(f2p)];
}
else {
[point, fake] = this.wNAF(n, affinePoint);
}
return JacobianPoint.normalizeZ([point, fake])[0];
}
toAffine(invZ = invert(this.z)) {
const invZ2 = invZ ** BigInt("2");
const x = mod(this.x * invZ2);
const y = mod(this.y * invZ2 * invZ);
return new Point(x, y);
}
}
JacobianPoint.BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, BigInt("1"));
JacobianPoint.ZERO = new JacobianPoint(BigInt("0"), BigInt("1"), BigInt("0"));
const pointPrecomputes = new WeakMap();
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
_setWindowSize(windowSize) {
this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this);
}
static fromCompressedHex(bytes) {
if (bytes.length !== 33) {
throw new TypeError(`Point.fromHex: compressed expects 66 bytes, not ${bytes.length * 2}`);
}
const x = bytesToNumber(bytes.slice(1));
const sqrY = weistrass(x);
let y = powMod(sqrY, P_DIV4_1, CURVE.P);
const isFirstByteOdd = (bytes[0] & 1) === 1;
const isYOdd = (y & BigInt("1")) === BigInt("1");
if (isFirstByteOdd !== isYOdd) {
y = mod(-y);
}
const point = new Point(x, y);
point.assertValidity();
return point;
}
static fromUncompressedHex(bytes) {
if (bytes.length !== 65) {
throw new TypeError(`Point.fromHex: uncompressed expects 130 bytes, not ${bytes.length * 2}`);
}
const x = bytesToNumber(bytes.slice(1, 33));
const y = bytesToNumber(bytes.slice(33));
const point = new Point(x, y);
point.assertValidity();
return point;
}
static fromHex(hex) {
const bytes = hex instanceof Uint8Array ? hex : hexToArray(hex);
const header = bytes[0];
if (header === 0x02 || header === 0x03)
return this.fromCompressedHex(bytes);
if (header === 0x04)
return this.fromUncompressedHex(bytes);
throw new TypeError('Point.fromHex: received invalid point');
}
static fromPrivateKey(privateKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey));
}
static fromSignature(msgHash, signature, recovery) {
const sign = normalizeSignature(signature);
const { r, s } = sign;
if (r === BigInt("0") || s === BigInt("0"))
return;
const rinv = invert(r, CURVE.n);
const h = typeof msgHash === 'string' ? hexToNumber(msgHash) : bytesToNumber(msgHash);
const P_ = Point.fromHex(`0${2 + (recovery & 1)}${pad64(r)}`);
const sP = JacobianPoint.fromAffine(P_).multiplyUnsafe(s);
const hG = JacobianPoint.BASE.multiply(h).negate();
const Q = sP.add(hG).multiplyUnsafe(rinv);
const point = Q.toAffine();
point.assertValidity();
return point;
}
toRawBytes(isCompressed = false) {
return hexToArray(this.toHex(isCompressed));
}
toHex(isCompressed = false) {
const x = pad64(this.x);
if (isCompressed) {
return `${this.y & BigInt("1") ? '03' : '02'}${x}`;
}
else {
return `04${x}${pad64(this.y)}`;
}
}
assertValidity() {
const { x, y } = this;
if (x === BigInt("0") || y === BigInt("0") || x >= CURVE.P || y >= CURVE.P) {
throw new TypeError('Point is not on elliptic curve');
}
const left = mod(y * y);
const right = weistrass(x);
const valid = (left - right) % CURVE.P === BigInt("0");
if (!valid)
throw new TypeError('Point is not on elliptic curve');
}
equals(other) {
return this.x === other.x && this.y === other.y;
}
negate() {
return new Point(this.x, mod(-this.y));
}
double() {
return JacobianPoint.fromAffine(this).double().toAffine();
}
add(other) {
return JacobianPoint.fromAffine(this).add(JacobianPoint.fromAffine(other)).toAffine();
}
subtract(other) {
return this.add(other.negate());
}
multiply(scalar) {
return JacobianPoint.fromAffine(this).multiply(scalar, this).toAffine();
}
}
exports.Point = Point;
Point.BASE = new Point(CURVE.Gx, CURVE.Gy);
Point.ZERO = new Point(BigInt("0"), BigInt("0"));
function sliceDer(s) {
return parseInt(s[0], 16) >= 8 ? '00' + s : s;
}
class SignResult {
constructor(r, s) {
this.r = r;
this.s = s;
}
static fromHex(hex) {
const str = hex instanceof Uint8Array ? bytesToHex(hex) : hex;
if (typeof str !== 'string')
throw new TypeError({}.toString.call(hex));
const check1 = str.slice(0, 2);
const length = parseByte(str.slice(2, 4));
const check2 = str.slice(4, 6);
if (check1 !== '30' || length !== str.length - 4 || check2 !== '02') {
throw new Error('SignResult.fromHex: Invalid signature');
}
const rLen = parseByte(str.slice(6, 8));
const rEnd = 8 + rLen;
const r = hexToNumber(str.slice(8, rEnd));
const check3 = str.slice(rEnd, rEnd + 2);
if (check3 !== '02') {
throw new Error('SignResult.fromHex: Invalid signature');
}
const sLen = parseByte(str.slice(rEnd + 2, rEnd + 4));
const sStart = rEnd + 4;
const s = hexToNumber(str.slice(sStart, sStart + sLen));
return new SignResult(r, s);
}
toRawBytes(isCompressed = false) {
return hexToArray(this.toHex(isCompressed));
}
toHex(isCompressed = false) {
const sHex = sliceDer(numberToHex(this.s));
if (isCompressed)
return sHex;
const rHex = sliceDer(numberToHex(this.r));
const rLen = numberToHex(rHex.length / 2);
const sLen = numberToHex(sHex.length / 2);
const length = numberToHex(rHex.length / 2 + sHex.length / 2 + 4);
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`;
}
}
exports.SignResult = SignResult;
function concatBytes(...arrays) {
if (arrays.length === 1)
return arrays[0];
const length = arrays.reduce((a, arr) => a + arr.length, 0);
const result = new Uint8Array(length);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const arr = arrays[i];
result.set(arr, pad);
pad += arr.length;
}
return result;
}
function bytesToHex(uint8a) {
let hex = '';
for (let i = 0; i < uint8a.length; i++) {
hex += uint8a[i].toString(16).padStart(2, '0');
}
return hex;
}
function pad64(num) {
return num.toString(16).padStart(64, '0');
}
function numberToHex(num) {
const hex = num.toString(16);
return hex.length & 1 ? `0${hex}` : hex;
}
function hexToNumber(hex) {
if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
}
return BigInt(`0x${hex}`);
}
function hexToArray(hex) {
hex = hex.length & 1 ? `0${hex}` : hex;
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
let j = i * 2;
array[i] = Number.parseInt(hex.slice(j, j + 2), 16);
}
return array;
}
function bytesToNumber(bytes) {
return hexToNumber(bytesToHex(bytes));
}
function parseByte(str) {
return Number.parseInt(str, 16) * 2;
}
function mod(a, b = CURVE.P) {
const result = a % b;
return result >= 0 ? result : b + result;
}
function powMod(x, power, order) {
let res = BigInt("1");
while (power > 0) {
if (power & BigInt("1")) {
res = mod(res * x, order);
}
power >>= BigInt("1");
x = mod(x * x, order);
}
return res;
}
function egcd(a, b) {
let [x, y, u, v] = [BigInt("0"), BigInt("1"), BigInt("1"), BigInt("0")];
while (a !== BigInt("0")) {
let q = b / a;
let r = b % a;
let m = x - u * q;
let n = y - v * q;
[b, a] = [a, r];
[x, y] = [u, v];
[u, v] = [m, n];
}
const gcd = b;
return [gcd, x, y];
}
function invert(number, modulo = CURVE.P) {
if (number === BigInt("0") || modulo <= BigInt("0")) {
throw new Error('invert: expected positive integers');
}
const [gcd, x] = egcd(mod(number, modulo), modulo);
if (gcd !== BigInt("1")) {
throw new Error('invert: does not exist');
}
return mod(x, modulo);
}
function invertBatch(nums, n = CURVE.P) {
const len = nums.length;
const scratch = new Array(len);
let acc = BigInt("1");
for (let i = 0; i < len; i++) {
if (nums[i] === BigInt("0"))
continue;
scratch[i] = acc;
acc = mod(acc * nums[i], n);
}
acc = invert(acc, n);
for (let i = len - 1; i >= 0; i--) {
if (nums[i] === BigInt("0"))
continue;
let tmp = mod(acc * nums[i], n);
nums[i] = mod(acc * scratch[i], n);
acc = tmp;
}
return nums;
}
function splitScalarEndo(k) {
const { n } = CURVE;
const a1 = BigInt("0x3086d221a7d46bcde86c90e49284eb15");
const b1 = -BigInt("0xe4437ed6010e88286f547fa90abfe4c3");
const a2 = BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8");
const b2 = a1;
const c1 = (b2 * k) / n;
const c2 = (-b1 * k) / n;
const k1 = k - c1 * a1 - c2 * a2;
const k2 = -c1 * b1 - c2 * b2;
const k1neg = k1 < 0;
const k2neg = k2 < 0;
return [k1neg, k1neg ? -k1 : k1, k2neg, k2neg ? -k2 : k2];
}
function truncateHash(hash) {
hash = typeof hash === 'string' ? hash : bytesToHex(hash);
let msg = hexToNumber(hash || '0');
const delta = (hash.length / 2) * 8 - PRIME_SIZE;
if (delta > 0) {
msg = msg >> BigInt(delta);
}
if (msg >= CURVE.n) {
msg -= CURVE.n;
}
return msg;
}
async function getQRSrfc6979(msgHash, privateKey) {
const num = typeof msgHash === 'string' ? hexToNumber(msgHash) : bytesToNumber(msgHash);
const h1 = hexToArray(pad64(num));
const x = hexToArray(pad64(privateKey));
const h1n = bytesToNumber(h1);
let v = new Uint8Array(32).fill(1);
let k = new Uint8Array(32).fill(0);
const b0 = Uint8Array.from([0x00]);
const b1 = Uint8Array.from([0x01]);
k = await exports.utils.hmacSha256(k, v, b0, x, h1);
v = await exports.utils.hmacSha256(k, v);
k = await exports.utils.hmacSha256(k, v, b1, x, h1);
v = await exports.utils.hmacSha256(k, v);
for (let i = 0; i < 1000; i++) {
v = await exports.utils.hmacSha256(k, v);
const T = bytesToNumber(v);
let qrs;
if (isValidPrivateKey(T) && (qrs = calcQRSFromK(T, h1n, privateKey))) {
return qrs;
}
k = await exports.utils.hmacSha256(k, v, b0);
v = await exports.utils.hmacSha256(k, v);
}
throw new TypeError('secp256k1: Tried 1,000 k values for sign(), all were invalid');
}
function isValidPrivateKey(privateKey) {
return 0 < privateKey && privateKey < CURVE.n;
}
function calcQRSFromK(k, msg, priv) {
const max = CURVE.n;
const q = Point.BASE.multiply(k);
const r = mod(q.x, max);
const s = mod(invert(k, max) * (msg + r * priv), max);
if (r === BigInt("0") || s === BigInt("0"))
return;
return [q, r, s];
}
function normalizePrivateKey(privateKey) {
if (!privateKey)
throw new Error(`Expected receive valid private key, not "${privateKey}"`);
let key;
if (privateKey instanceof Uint8Array) {
key = bytesToNumber(privateKey);
}
else if (typeof privateKey === 'string') {
key = hexToNumber(privateKey);
}
else {
key = BigInt(privateKey);
}
return key;
}
function normalizePublicKey(publicKey) {
return publicKey instanceof Point ? publicKey : Point.fromHex(publicKey);
}
function normalizeSignature(signature) {
return signature instanceof SignResult ? signature : SignResult.fromHex(signature);
}
function getPublicKey(privateKey, isCompressed = false) {
const point = Point.fromPrivateKey(privateKey);
if (typeof privateKey === 'string') {
return point.toHex(isCompressed);
}
return point.toRawBytes(isCompressed);
}
exports.getPublicKey = getPublicKey;
function recoverPublicKey(msgHash, signature, recovery) {
const point = Point.fromSignature(msgHash, signature, recovery);
if (!point)
return;
return typeof msgHash === 'string' ? point.toHex() : point.toRawBytes();
}
exports.recoverPublicKey = recoverPublicKey;
function isPub(item) {
const arr = item instanceof Uint8Array;
const str = typeof item === 'string';
const len = (arr || str) && item.length;
if (arr)
return len === 33 || len === 65;
if (str)
return len === 66 || len === 130;
if (item instanceof Point)
return true;
return false;
}
function getSharedSecret(privateA, publicB, isCompressed = false) {
if (isPub(privateA) && !isPub(publicB)) {
[privateA, publicB] = [publicB, privateA];
}
else if (!isPub(publicB)) {
throw new Error('Received invalid keys');
}
const b = publicB instanceof Point ? publicB : Point.fromHex(publicB);
b.assertValidity();
const shared = b.multiply(normalizePrivateKey(privateA));
return typeof privateA === 'string'
? shared.toHex(isCompressed)
: shared.toRawBytes(isCompressed);
}
exports.getSharedSecret = getSharedSecret;
async function sign(msgHash, privateKey, { recovered, canonical } = {}) {
if (msgHash == null)
throw new Error(`Expected valid msgHash, not "${msgHash}"`);
const priv = normalizePrivateKey(privateKey);
const [q, r, s] = await getQRSrfc6979(msgHash, priv);
let recovery = (q.x === r ? 0 : 2) | Number(q.y & BigInt("1"));
let adjustedS = s;
const HIGH_NUMBER = CURVE.n >> BigInt("1");
if (s > HIGH_NUMBER && canonical) {
adjustedS = CURVE.n - s;
recovery ^= 1;
}
const sig = new SignResult(r, adjustedS);
const hashed = typeof msgHash === 'string' ? sig.toHex() : sig.toRawBytes();
return recovered ? [hashed, recovery] : hashed;
}
exports.sign = sign;
function verify(signature, msgHash, publicKey) {
const h = truncateHash(msgHash);
const { r, s } = normalizeSignature(signature);
const pubKey = JacobianPoint.fromAffine(normalizePublicKey(publicKey));
const s1 = invert(s, CURVE.n);
const Ghs1 = JacobianPoint.BASE.multiply(mod(h * s1, CURVE.n));
const Prs1 = pubKey.multiplyUnsafe(mod(r * s1, CURVE.n));
const res = Ghs1.add(Prs1).toAffine();
return res.x === r;
}
exports.verify = verify;
Point.BASE._setWindowSize(8);
exports.utils = {
isValidPrivateKey(privateKey) {
return isValidPrivateKey(normalizePrivateKey(privateKey));
},
randomPrivateKey: (bytesLength = 32) => {
if (typeof window == 'object' && 'crypto' in window) {
return window.crypto.getRandomValues(new Uint8Array(bytesLength));
}
else if (typeof process === 'object' && 'node' in process.versions) {
const { randomBytes } = require('crypto');
return new Uint8Array(randomBytes(bytesLength).buffer);
}
else {
throw new Error("The environment doesn't have randomBytes function");
}
},
hmacSha256: async (key, ...messages) => {
if (typeof window == 'object' && 'crypto' in window) {
const ckey = await window.crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign', 'verify']);
const message = concatBytes(...messages);
const buffer = await window.crypto.subtle.sign('HMAC', ckey, message);
return new Uint8Array(buffer);
}
else if (typeof process === 'object' && 'node' in process.versions) {
const { createHmac, randomBytes } = require('crypto');
const hash = createHmac('sha256', key);
for (let message of messages) {
hash.update(message);
}
return Uint8Array.from(hash.digest());
}
else {
throw new Error("The environment doesn't have hmac-sha256 function");
}
},
precompute(windowSize = 8, point = Point.BASE) {
const cached = point === Point.BASE ? point : new Point(point.x, point.y);
cached._setWindowSize(windowSize);
cached.multiply(BigInt("3"));
return cached;
},
};