mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 16:28:06 +00:00
461 lines
12 KiB
TypeScript
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();
|
|
}
|