Files
ritmex-bot/src/exchanges/lighter/crypto/goldilocks-fp5.ts
T

461 lines
12 KiB
TypeScript

import { Fp, GOLDILOCKS_MODULUS, addMany, powers as fpPowers } from "./goldilocks";
export type Fp5Tuple = [Fp, Fp, Fp, Fp, Fp];
function expectLength(bytes: Uint8Array, expected: number): void {
if (bytes.length !== expected) {
throw new Error(`Expected ${expected} bytes, got ${bytes.length}`);
}
}
const BYTE_LENGTH = 5 * 8;
export const FP5_W = new Fp(3n);
export const FP5_DTH_ROOT = new Fp(1041288259238279555n);
const THREE = new Fp(3n);
export class Fp5 {
readonly c0: Fp;
readonly c1: Fp;
readonly c2: Fp;
readonly c3: Fp;
readonly c4: Fp;
constructor(coeffs: Fp5Tuple) {
[this.c0, this.c1, this.c2, this.c3, this.c4] = coeffs;
}
static ZERO = new Fp5([Fp.ZERO, Fp.ZERO, Fp.ZERO, Fp.ZERO, Fp.ZERO]);
static ONE = new Fp5([Fp.ONE, Fp.ZERO, Fp.ZERO, Fp.ZERO, Fp.ZERO]);
static fromFp(value: Fp): Fp5 {
return new Fp5([value.clone(), Fp.ZERO, Fp.ZERO, Fp.ZERO, Fp.ZERO]);
}
static fromUint64Array(values: ArrayLike<bigint | number>): Fp5 {
if (values.length !== 5) {
throw new Error("Fp5.fromUint64Array expects 5 values");
}
const get = (index: number): bigint | number => {
const value = values[index];
if (value === undefined) {
throw new Error("Fp5.fromUint64Array missing value");
}
return value;
};
const arr: [bigint | number, bigint | number, bigint | number, bigint | number, bigint | number] = [
get(0),
get(1),
get(2),
get(3),
get(4),
];
return new Fp5([
new Fp(arr[0]),
new Fp(arr[1]),
new Fp(arr[2]),
new Fp(arr[3]),
new Fp(arr[4]),
]);
}
toTuple(): Fp5Tuple {
return [this.c0, this.c1, this.c2, this.c3, this.c4];
}
toBytes(): Uint8Array {
const out = new Uint8Array(BYTE_LENGTH);
out.set(this.c0.toBytesLE(), 0);
out.set(this.c1.toBytesLE(), 8);
out.set(this.c2.toBytesLE(), 16);
out.set(this.c3.toBytesLE(), 24);
out.set(this.c4.toBytesLE(), 32);
return out;
}
static fromBytes(bytes: Uint8Array): Fp5 {
expectLength(bytes, BYTE_LENGTH);
return new Fp5([
Fp.fromBytesLE(bytes.subarray(0, 8)),
Fp.fromBytesLE(bytes.subarray(8, 16)),
Fp.fromBytesLE(bytes.subarray(16, 24)),
Fp.fromBytesLE(bytes.subarray(24, 32)),
Fp.fromBytesLE(bytes.subarray(32, 40)),
]);
}
equals(other: Fp5): boolean {
return (
this.c0.equals(other.c0) &&
this.c1.equals(other.c1) &&
this.c2.equals(other.c2) &&
this.c3.equals(other.c3) &&
this.c4.equals(other.c4)
);
}
isZero(): boolean {
return this.equals(Fp5.ZERO);
}
clone(): Fp5 {
return new Fp5([
this.c0.clone(),
this.c1.clone(),
this.c2.clone(),
this.c3.clone(),
this.c4.clone(),
]);
}
add(other: Fp5): Fp5 {
return new Fp5([
this.c0.add(other.c0),
this.c1.add(other.c1),
this.c2.add(other.c2),
this.c3.add(other.c3),
this.c4.add(other.c4),
]);
}
sub(other: Fp5): Fp5 {
return new Fp5([
this.c0.sub(other.c0),
this.c1.sub(other.c1),
this.c2.sub(other.c2),
this.c3.sub(other.c3),
this.c4.sub(other.c4),
]);
}
neg(): Fp5 {
return new Fp5([
this.c0.neg(),
this.c1.neg(),
this.c2.neg(),
this.c3.neg(),
this.c4.neg(),
]);
}
mul(other: Fp5): Fp5 {
const w = FP5_W;
const a0b0 = this.c0.mul(other.c0);
const a1b4 = this.c1.mul(other.c4);
const a2b3 = this.c2.mul(other.c3);
const a3b2 = this.c3.mul(other.c2);
const a4b1 = this.c4.mul(other.c1);
const added0 = addMany([a1b4, a2b3, a3b2, a4b1]);
const muld0 = added0.mul(w);
const c0 = a0b0.add(muld0);
const a0b1 = this.c0.mul(other.c1);
const a1b0 = this.c1.mul(other.c0);
const a2b4 = this.c2.mul(other.c4);
const a3b3 = this.c3.mul(other.c3);
const a4b2 = this.c4.mul(other.c2);
const added1 = addMany([a2b4, a3b3, a4b2]);
const muld1 = added1.mul(w);
const c1 = addMany([a0b1, a1b0, muld1]);
const a0b2 = this.c0.mul(other.c2);
const a1b1 = this.c1.mul(other.c1);
const a2b0 = this.c2.mul(other.c0);
const a3b4 = this.c3.mul(other.c4);
const a4b3 = this.c4.mul(other.c3);
const added2 = addMany([a3b4, a4b3]);
const muld2 = added2.mul(w);
const c2 = addMany([a0b2, a1b1, a2b0, muld2]);
const a0b3 = this.c0.mul(other.c3);
const a1b2 = this.c1.mul(other.c2);
const a2b1 = this.c2.mul(other.c1);
const a3b0 = this.c3.mul(other.c0);
const a4b4 = this.c4.mul(other.c4);
const muld3 = a4b4.mul(w);
const c3 = addMany([a0b3, a1b2, a2b1, a3b0, muld3]);
const a0b4 = this.c0.mul(other.c4);
const a1b3 = this.c1.mul(other.c3);
const a2b2 = this.c2.mul(other.c2);
const a3b1 = this.c3.mul(other.c1);
const a4b0 = this.c4.mul(other.c0);
const c4 = addMany([a0b4, a1b3, a2b2, a3b1, a4b0]);
return new Fp5([c0, c1, c2, c3, c4]);
}
square(): Fp5 {
const w = FP5_W;
const doubleW = FP5_W.add(FP5_W);
const a0s = this.c0.mul(this.c0);
const a1a4 = this.c1.mul(this.c4);
const a2a3 = this.c2.mul(this.c3);
const added0 = addMany([a1a4, a2a3]);
const muld0 = added0.mul(doubleW);
const c0 = a0s.add(muld0);
const a0Double = this.c0.add(this.c0);
const a0Doublea1 = a0Double.mul(this.c1);
const a2a4DoubleW = this.c2.mul(this.c4).mul(doubleW);
const a3a3w = this.c3.mul(this.c3).mul(w);
const c1 = addMany([a0Doublea1, a2a4DoubleW, a3a3w]);
const a0Doublea2 = a0Double.mul(this.c2);
const a1Square = this.c1.mul(this.c1);
const a4a3DoubleW = this.c4.mul(this.c3).mul(doubleW);
const c2 = addMany([a0Doublea2, a1Square, a4a3DoubleW]);
const a1Double = this.c1.add(this.c1);
const a0Doublea3 = a0Double.mul(this.c3);
const a1Doublea2 = a1Double.mul(this.c2);
const a4SquareW = this.c4.mul(this.c4).mul(w);
const c3 = addMany([a0Doublea3, a1Doublea2, a4SquareW]);
const a0Doublea4 = a0Double.mul(this.c4);
const a1Doublea3 = a1Double.mul(this.c3);
const a2Square = this.c2.mul(this.c2);
const c4 = addMany([a0Doublea4, a1Doublea3, a2Square]);
return new Fp5([c0, c1, c2, c3, c4]);
}
expPowerOf2(power: number): Fp5 {
let result = this.clone();
for (let i = 0; i < power; i++) {
result = result.square();
}
return result;
}
double(): Fp5 {
return this.add(this);
}
triple(): Fp5 {
return new Fp5([
this.c0.mul(THREE),
this.c1.mul(THREE),
this.c2.mul(THREE),
this.c3.mul(THREE),
this.c4.mul(THREE),
]);
}
scalarMul(scalar: Fp): Fp5 {
return new Fp5([
this.c0.mul(scalar),
this.c1.mul(scalar),
this.c2.mul(scalar),
this.c3.mul(scalar),
this.c4.mul(scalar),
]);
}
inverseOrZero(): Fp5 {
if (this.isZero()) return Fp5.ZERO;
const d = this.frobenius();
const e = this.mul(d.frobenius());
const f = e.mul(e.repeatedFrobenius(2));
const a0b0 = this.c0.mul(f.c0);
const a1b4 = this.c1.mul(f.c4);
const a2b3 = this.c2.mul(f.c3);
const a3b2 = this.c3.mul(f.c2);
const a4b1 = this.c4.mul(f.c1);
const added = addMany([a1b4, a2b3, a3b2, a4b1]);
const muld = added.mul(FP5_W);
const g = a0b0.add(muld);
const gInv = g.inverse();
return f.scalarMul(gInv);
}
div(other: Fp5): Fp5 {
const inv = other.inverseOrZero();
if (inv.isZero()) {
throw new Error("Division by zero in Fp5");
}
return this.mul(inv);
}
frobenius(): Fp5 {
return this.repeatedFrobenius(1);
}
repeatedFrobenius(count: number): Fp5 {
if (count === 0) return this;
const reduced = count % 5;
if (reduced === 0) return this;
let z0 = FP5_DTH_ROOT;
for (let i = 1; i < reduced; i++) {
z0 = z0.mul(FP5_DTH_ROOT);
}
const powerArray = fpPowers(z0, 5) as [Fp, Fp, Fp, Fp, Fp];
const [p0, p1, p2, p3, p4] = powerArray;
return new Fp5([
this.c0.mul(p0),
this.c1.mul(p1),
this.c2.mul(p2),
this.c3.mul(p3),
this.c4.mul(p4),
]);
}
legendre(): Fp {
const frob1 = this.frobenius();
const frob2 = frob1.frobenius();
const frob1TimesFrob2 = frob1.mul(frob2);
const frob2Frob1TimesFrob2 = frob1TimesFrob2.repeatedFrobenius(2);
const xrExt = this.mul(frob1TimesFrob2).mul(frob2Frob1TimesFrob2);
const xr = new Fp(xrExt.c0.toBigInt());
const xr31 = xr.pow(1n << 31n);
const xr31Inv = xr31.isZero() ? Fp.ZERO : xr31.inverse();
const xr63 = xr31.pow(1n << 32n);
return xr63.mul(xr31Inv);
}
sqrt(): { value: Fp5; exists: boolean } {
const v = this.expPowerOf2(31);
const d = this.mul(v.expPowerOf2(32)).mul(v.inverseOrZero());
const e = d.mul(d.repeatedFrobenius(2)).frobenius();
const f = e.square();
const x1f4 = this.c1.mul(f.c4);
const x2f3 = this.c2.mul(f.c3);
const x3f2 = this.c3.mul(f.c2);
const x4f1 = this.c4.mul(f.c1);
const added = addMany([x1f4, x2f3, x3f2, x4f1]);
const muld = added.mul(THREE);
const x0f0 = this.c0.mul(f.c0);
const g = x0f0.add(muld);
const s = sqrtFp(g);
if (!s) {
return { value: Fp5.ZERO, exists: false };
}
const eInv = e.inverseOrZero();
const sFp5 = Fp5.fromFp(s);
return { value: sFp5.mul(eInv), exists: true };
}
canonicalSqrt(): { value: Fp5; exists: boolean } {
const { value, exists } = this.sqrt();
if (!exists) return { value: Fp5.ZERO, exists: false };
return { value: sgn0(value) ? value.neg() : value, exists: true };
}
}
function sgn0(x: Fp5): boolean {
let sign = false;
let zero = true;
for (const limb of [x.c0, x.c1, x.c2, x.c3, x.c4]) {
const limbSign = (limb.toBigInt() & 1n) === 0n;
const limbZero = limb.isZero();
sign = sign || (zero && limbSign);
zero = zero && limbZero;
}
return sign;
}
function sqrtFp(value: Fp): Fp | null {
if (value.isZero()) return Fp.ZERO;
const p = GOLDILOCKS_MODULUS;
const leg = value.pow((p - 1n) / 2n);
if (leg.isZero()) return Fp.ZERO;
const legVal = leg.toBigInt();
if (legVal === p - 1n) return null;
let q = p - 1n;
let s = 0n;
while ((q & 1n) === 0n) {
q >>= 1n;
s += 1n;
}
let z = 2n;
while (true) {
const zLeg = new Fp(z).pow((p - 1n) / 2n).toBigInt();
if (zLeg === p - 1n) break;
z += 1n;
}
let c = new Fp(z).pow(q);
let x = value.pow((q + 1n) >> 1n);
let t = value.pow(q);
let m = s;
while (t.toBigInt() !== 1n) {
let i = 1n;
let t2i = t.mul(t);
while (t2i.toBigInt() !== 1n) {
t2i = t2i.mul(t2i);
i += 1n;
if (i === m) return null;
}
const b = c.pow(1n << (m - i - 1n));
x = x.mul(b);
c = b.mul(b);
t = t.mul(c);
m = i;
}
return x;
}
export const FP5_ZERO = Fp5.ZERO;
export const FP5_ONE = Fp5.ONE;
export const FP5_TWO = Fp5.fromFp(new Fp(2n));
export function fp5Add(a: Fp5, ...others: Fp5[]): Fp5 {
let acc = a;
for (const other of others) {
acc = acc.add(other);
}
return acc;
}
export function fp5Sub(a: Fp5, b: Fp5): Fp5 {
return a.sub(b);
}
export function fp5Mul(a: Fp5, b: Fp5): Fp5 {
return a.mul(b);
}
export function fp5Square(a: Fp5): Fp5 {
return a.square();
}
export function fp5Double(a: Fp5): Fp5 {
return a.double();
}
export function fp5ScalarMul(a: Fp5, scalar: Fp): Fp5 {
return a.scalarMul(scalar);
}
export function fp5InverseOrZero(a: Fp5): Fp5 {
return a.inverseOrZero();
}
export function fp5Frobenius(a: Fp5): Fp5 {
return a.frobenius();
}
export function fp5RepeatedFrobenius(a: Fp5, count: number): Fp5 {
return a.repeatedFrobenius(count);
}
export function fp5Legendre(a: Fp5): Fp {
return a.legendre();
}
export function fp5CanonicalSqrt(a: Fp5): { value: Fp5; exists: boolean } {
return a.canonicalSqrt();
}