mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
feat: 重构 Lighter 策略,更新签名桥接路径,移除不必要的文件,添加新的签名库支持
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
import { Fp } from "./goldilocks";
|
||||
import {
|
||||
Fp5,
|
||||
FP5_ZERO,
|
||||
FP5_ONE,
|
||||
FP5_TWO,
|
||||
FP5_W,
|
||||
fp5Add,
|
||||
fp5Mul,
|
||||
fp5ScalarMul,
|
||||
fp5Double,
|
||||
fp5InverseOrZero,
|
||||
} from "./goldilocks-fp5";
|
||||
import { Scalar } from "./scalar";
|
||||
|
||||
const B1 = 263n;
|
||||
const B = Fp5.fromUint64Array([0n, B1, 0n, 0n, 0n]);
|
||||
const B_MUL2 = Fp5.fromUint64Array([0n, 2n * B1, 0n, 0n, 0n]);
|
||||
const B_MUL4 = Fp5.fromUint64Array([0n, 4n * B1, 0n, 0n, 0n]);
|
||||
const B_MUL16 = Fp5.fromUint64Array([0n, 16n * B1, 0n, 0n, 0n]);
|
||||
const A = Fp5.fromUint64Array([2n, 0n, 0n, 0n, 0n]);
|
||||
const FOUR_CONST = Fp5.fromUint64Array([4n, 0n, 0n, 0n, 0n]);
|
||||
|
||||
export class ECPoint {
|
||||
constructor(
|
||||
readonly x: Fp5,
|
||||
readonly z: Fp5,
|
||||
readonly u: Fp5,
|
||||
readonly t: Fp5,
|
||||
) {}
|
||||
|
||||
clone(): ECPoint {
|
||||
return new ECPoint(this.x.clone(), this.z.clone(), this.u.clone(), this.t.clone());
|
||||
}
|
||||
|
||||
static neutral(): ECPoint {
|
||||
return new ECPoint(FP5_ZERO, FP5_ONE, FP5_ZERO, FP5_ONE);
|
||||
}
|
||||
|
||||
static generator(): ECPoint {
|
||||
return new ECPoint(
|
||||
Fp5.fromUint64Array([
|
||||
12883135586176881569n,
|
||||
4356519642755055268n,
|
||||
5248930565894896907n,
|
||||
2165973894480315022n,
|
||||
2448410071095648785n,
|
||||
]),
|
||||
FP5_ONE,
|
||||
FP5_ONE,
|
||||
Fp5.fromUint64Array([4n, 0n, 0n, 0n, 0n]),
|
||||
);
|
||||
}
|
||||
|
||||
isNeutral(): boolean {
|
||||
return this.u.equals(FP5_ZERO);
|
||||
}
|
||||
|
||||
encode(): Fp5 {
|
||||
return fp5Mul(this.t, fp5InverseOrZero(this.u));
|
||||
}
|
||||
|
||||
add(other: ECPoint): ECPoint {
|
||||
if (this.isNeutral()) return other.clone();
|
||||
if (other.isNeutral()) return this.clone();
|
||||
|
||||
const x1 = this.x;
|
||||
const z1 = this.z;
|
||||
const u1 = this.u;
|
||||
const t1 = this.t;
|
||||
|
||||
const x2 = other.x;
|
||||
const z2 = other.z;
|
||||
const u2 = other.u;
|
||||
const t2 = other.t;
|
||||
|
||||
const t1_ = fp5Mul(x1, x2);
|
||||
const t2_ = fp5Mul(z1, z2);
|
||||
const t3 = fp5Mul(u1, u2);
|
||||
const t4 = fp5Mul(t1, t2);
|
||||
const t5 = fp5SubMul(fp5Add(x1, z1), fp5Add(x2, z2), fp5Add(t1_, t2_));
|
||||
const t6 = fp5SubMul(fp5Add(u1, t1), fp5Add(u2, t2), fp5Add(t3, t4));
|
||||
const t7 = fp5Add(t1_, fp5Mul(t2_, B));
|
||||
const t8 = fp5Mul(t4, t7);
|
||||
const t9 = fp5Mul(t3, fp5Add(fp5Mul(t5, B_MUL2), fp5Double(t7)));
|
||||
const t10 = fp5Mul(fp5Add(t4, fp5Double(t3)), fp5Add(t5, t7));
|
||||
|
||||
const xNew = fp5Mul(fp5Sub(t10, t8), B);
|
||||
const zNew = fp5Sub(t8, t9);
|
||||
const uNew = fp5Mul(t6, fp5Sub(fp5Mul(t2_, B), t1_));
|
||||
const tNew = fp5Add(t8, t9);
|
||||
|
||||
return new ECPoint(xNew, zNew, uNew, tNew);
|
||||
}
|
||||
|
||||
double(): ECPoint {
|
||||
if (this.isNeutral()) return this.clone();
|
||||
|
||||
const x = this.x;
|
||||
const z = this.z;
|
||||
const u = this.u;
|
||||
const t = this.t;
|
||||
|
||||
const t1 = fp5Mul(z, t);
|
||||
const t2 = fp5Mul(t1, t);
|
||||
const x1 = fp5Mul(t2, t2);
|
||||
const z1 = fp5Mul(t1, u);
|
||||
const t3 = fp5Mul(u, u);
|
||||
const w1 = fp5Sub(t2, fp5Mul(fp5Double(fp5Add(x, z)), t3));
|
||||
const t4 = fp5Mul(z1, z1);
|
||||
|
||||
const xNew = fp5Mul(t4, B_MUL4);
|
||||
const zNew = fp5Mul(w1, w1);
|
||||
const uNew = fp5Sub(fp5Mul(fp5Add(w1, z1), fp5Add(w1, z1)), fp5Add(t4, zNew));
|
||||
const tNew = fp5Sub(
|
||||
fp5Double(x1),
|
||||
fp5Add(fp5Mul(t4, FOUR_CONST), zNew),
|
||||
);
|
||||
|
||||
return new ECPoint(xNew, zNew, uNew, tNew);
|
||||
}
|
||||
|
||||
mul(scalar: Scalar): ECPoint {
|
||||
let result = ECPoint.neutral();
|
||||
let addend = this.clone();
|
||||
let k = scalar.toBigInt();
|
||||
while (k > 0n) {
|
||||
if (k & 1n) {
|
||||
result = result.add(addend);
|
||||
}
|
||||
addend = addend.double();
|
||||
k >>= 1n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function fp5Sub(a: Fp5, b: Fp5): Fp5 {
|
||||
return a.sub(b);
|
||||
}
|
||||
|
||||
function fp5SubMul(a: Fp5, b: Fp5, subtract: Fp5): Fp5 {
|
||||
return fp5Mul(a, b).sub(subtract);
|
||||
}
|
||||
@@ -1,460 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
const TWO_POW_32 = 1n << 32n;
|
||||
const TWO_POW_64 = 1n << 64n;
|
||||
export const GOLDILOCKS_MODULUS = TWO_POW_64 - TWO_POW_32 + 1n;
|
||||
const BYTE_LENGTH = 8;
|
||||
|
||||
function mod(value: bigint): bigint {
|
||||
let v = value % GOLDILOCKS_MODULUS;
|
||||
if (v < 0n) v += GOLDILOCKS_MODULUS;
|
||||
return v;
|
||||
}
|
||||
|
||||
export class Fp {
|
||||
readonly value: bigint;
|
||||
|
||||
constructor(value: bigint | number) {
|
||||
this.value = mod(typeof value === "number" ? BigInt(value) : value);
|
||||
}
|
||||
|
||||
static readonly ZERO = new Fp(0n);
|
||||
static readonly ONE = new Fp(1n);
|
||||
|
||||
static fromBytesLE(bytes: Uint8Array): Fp {
|
||||
if (bytes.length !== BYTE_LENGTH) {
|
||||
throw new Error(`Goldilocks element expects 8 bytes, got ${bytes.length}`);
|
||||
}
|
||||
let acc = 0n;
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
const byte = bytes[i];
|
||||
if (byte === undefined) throw new Error("Unexpected undefined byte when reading Goldilocks element");
|
||||
acc |= BigInt(byte) << BigInt(8 * i);
|
||||
}
|
||||
return new Fp(acc);
|
||||
}
|
||||
|
||||
toBytesLE(): Uint8Array {
|
||||
let v = this.value;
|
||||
const out = new Uint8Array(BYTE_LENGTH);
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
out[i] = Number(v & 0xffn);
|
||||
v >>= 8n;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
toBigInt(): bigint {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
toNumber(): number {
|
||||
return Number(this.value);
|
||||
}
|
||||
|
||||
add(...others: Fp[]): Fp {
|
||||
let acc = this.value;
|
||||
for (const other of others) acc += other.value;
|
||||
return new Fp(acc);
|
||||
}
|
||||
|
||||
sub(other: Fp): Fp {
|
||||
return new Fp(this.value - other.value);
|
||||
}
|
||||
|
||||
neg(): Fp {
|
||||
return new Fp(this.value === 0n ? 0n : GOLDILOCKS_MODULUS - this.value);
|
||||
}
|
||||
|
||||
mul(...others: Fp[]): Fp {
|
||||
let acc = this.value;
|
||||
for (const other of others) acc = mod(acc * other.value);
|
||||
return new Fp(acc);
|
||||
}
|
||||
|
||||
square(): Fp {
|
||||
return new Fp(mod(this.value * this.value));
|
||||
}
|
||||
|
||||
double(): Fp {
|
||||
return new Fp(this.value << 1n);
|
||||
}
|
||||
|
||||
inverse(): Fp {
|
||||
if (this.isZero()) {
|
||||
throw new Error("Cannot invert zero in Goldilocks field");
|
||||
}
|
||||
return this.pow(GOLDILOCKS_MODULUS - 2n);
|
||||
}
|
||||
|
||||
pow(exponent: bigint): Fp {
|
||||
let result = 1n;
|
||||
let base = this.value;
|
||||
let exp = exponent;
|
||||
while (exp > 0n) {
|
||||
if (exp & 1n) result = mod(result * base);
|
||||
base = mod(base * base);
|
||||
exp >>= 1n;
|
||||
}
|
||||
return new Fp(result);
|
||||
}
|
||||
|
||||
isZero(): boolean {
|
||||
return this.value === 0n;
|
||||
}
|
||||
|
||||
isOne(): boolean {
|
||||
return this.value === 1n;
|
||||
}
|
||||
|
||||
equals(other: Fp): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
clone(): Fp {
|
||||
return new Fp(this.value);
|
||||
}
|
||||
|
||||
static random(): Fp {
|
||||
while (true) {
|
||||
const buf = randomBytes(BYTE_LENGTH);
|
||||
let acc = 0n;
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
const byte = buf[i];
|
||||
if (byte === undefined) throw new Error("Unexpected undefined byte when sampling Goldilocks element");
|
||||
acc |= BigInt(byte) << BigInt(8 * i);
|
||||
}
|
||||
if (acc < GOLDILOCKS_MODULUS) {
|
||||
return new Fp(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fromUint32(value: number): Fp {
|
||||
return new Fp(BigInt(value >>> 0));
|
||||
}
|
||||
|
||||
static fromUint64(value: bigint | number): Fp {
|
||||
return new Fp(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function addMany(elements: readonly Fp[]): Fp {
|
||||
let acc = 0n;
|
||||
for (const el of elements) acc += el.value;
|
||||
return new Fp(acc);
|
||||
}
|
||||
|
||||
export function arrayToBytesLE(elements: readonly Fp[]): Uint8Array {
|
||||
const out = new Uint8Array(elements.length * BYTE_LENGTH);
|
||||
elements.forEach((elem, idx) => {
|
||||
out.set(elem.toBytesLE(), idx * BYTE_LENGTH);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
export function arrayFromBytesLE(bytes: Uint8Array): Fp[] {
|
||||
if (bytes.length % BYTE_LENGTH !== 0) {
|
||||
throw new Error("Goldilocks array bytes length must be multiple of 8");
|
||||
}
|
||||
const out: Fp[] = [];
|
||||
for (let i = 0; i < bytes.length; i += BYTE_LENGTH) {
|
||||
out.push(Fp.fromBytesLE(bytes.subarray(i, i + BYTE_LENGTH)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function powers(base: Fp, count: number): Fp[] {
|
||||
if (count <= 0) return [];
|
||||
const result = new Array<Fp>(count);
|
||||
result[0] = Fp.ONE;
|
||||
for (let i = 1; i < count; i++) {
|
||||
const prev = result[i - 1];
|
||||
if (!prev) throw new Error("unexpected undefined in goldilocks powers");
|
||||
result[i] = prev.mul(base);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const GOLDILOCKS_BYTE_LENGTH = BYTE_LENGTH;
|
||||
@@ -1,320 +0,0 @@
|
||||
import { Fp } from "./goldilocks";
|
||||
import { Fp5 } from "./goldilocks-fp5";
|
||||
|
||||
const WIDTH = 12;
|
||||
const RATE = 8;
|
||||
const ROUNDS_F = 8;
|
||||
const ROUNDS_F_HALF = 4;
|
||||
const ROUNDS_P = 22;
|
||||
|
||||
const EXTERNAL_CONSTANTS: Fp[][] = [
|
||||
[
|
||||
new Fp(15492826721047263190n),
|
||||
new Fp(11728330187201910315n),
|
||||
new Fp(8836021247773420868n),
|
||||
new Fp(16777404051263952451n),
|
||||
new Fp(5510875212538051896n),
|
||||
new Fp(6173089941271892285n),
|
||||
new Fp(2927757366422211339n),
|
||||
new Fp(10340958981325008808n),
|
||||
new Fp(8541987352684552425n),
|
||||
new Fp(9739599543776434497n),
|
||||
new Fp(15073950188101532019n),
|
||||
new Fp(12084856431752384512n),
|
||||
],
|
||||
[
|
||||
new Fp(4584713381960671270n),
|
||||
new Fp(8807052963476652830n),
|
||||
new Fp(54136601502601741n),
|
||||
new Fp(4872702333905478703n),
|
||||
new Fp(5551030319979516287n),
|
||||
new Fp(12889366755535460989n),
|
||||
new Fp(16329242193178844328n),
|
||||
new Fp(412018088475211848n),
|
||||
new Fp(10505784623379650541n),
|
||||
new Fp(9758812378619434837n),
|
||||
new Fp(7421979329386275117n),
|
||||
new Fp(375240370024755551n),
|
||||
],
|
||||
[
|
||||
new Fp(3331431125640721931n),
|
||||
new Fp(15684937309956309981n),
|
||||
new Fp(578521833432107983n),
|
||||
new Fp(14379242000670861838n),
|
||||
new Fp(17922409828154900976n),
|
||||
new Fp(8153494278429192257n),
|
||||
new Fp(15904673920630731971n),
|
||||
new Fp(11217863998460634216n),
|
||||
new Fp(3301540195510742136n),
|
||||
new Fp(9937973023749922003n),
|
||||
new Fp(3059102938155026419n),
|
||||
new Fp(1895288289490976132n),
|
||||
],
|
||||
[
|
||||
new Fp(5580912693628927540n),
|
||||
new Fp(10064804080494788323n),
|
||||
new Fp(9582481583369602410n),
|
||||
new Fp(10186259561546797986n),
|
||||
new Fp(247426333829703916n),
|
||||
new Fp(13193193905461376067n),
|
||||
new Fp(6386232593701758044n),
|
||||
new Fp(17954717245501896472n),
|
||||
new Fp(1531720443376282699n),
|
||||
new Fp(2455761864255501970n),
|
||||
new Fp(11234429217864304495n),
|
||||
new Fp(4746959618548874102n),
|
||||
],
|
||||
[
|
||||
new Fp(13571697342473846203n),
|
||||
new Fp(17477857865056504753n),
|
||||
new Fp(15963032953523553760n),
|
||||
new Fp(16033593225279635898n),
|
||||
new Fp(14252634232868282405n),
|
||||
new Fp(8219748254835277737n),
|
||||
new Fp(7459165569491914711n),
|
||||
new Fp(15855939513193752003n),
|
||||
new Fp(16788866461340278896n),
|
||||
new Fp(7102224659693946577n),
|
||||
new Fp(3024718005636976471n),
|
||||
new Fp(13695468978618890430n),
|
||||
],
|
||||
[
|
||||
new Fp(8214202050877825436n),
|
||||
new Fp(2670727992739346204n),
|
||||
new Fp(16259532062589659211n),
|
||||
new Fp(11869922396257088411n),
|
||||
new Fp(3179482916972760137n),
|
||||
new Fp(13525476046633427808n),
|
||||
new Fp(3217337278042947412n),
|
||||
new Fp(14494689598654046340n),
|
||||
new Fp(15837379330312175383n),
|
||||
new Fp(8029037639801151344n),
|
||||
new Fp(2153456285263517937n),
|
||||
new Fp(8301106462311849241n),
|
||||
],
|
||||
[
|
||||
new Fp(13294194396455217955n),
|
||||
new Fp(17394768489610594315n),
|
||||
new Fp(12847609130464867455n),
|
||||
new Fp(14015739446356528640n),
|
||||
new Fp(5879251655839607853n),
|
||||
new Fp(9747000124977436185n),
|
||||
new Fp(8950393546890284269n),
|
||||
new Fp(10765765936405694368n),
|
||||
new Fp(14695323910334139959n),
|
||||
new Fp(16366254691123000864n),
|
||||
new Fp(15292774414889043182n),
|
||||
new Fp(10910394433429313384n),
|
||||
],
|
||||
[
|
||||
new Fp(17253424460214596184n),
|
||||
new Fp(3442854447664030446n),
|
||||
new Fp(3005570425335613727n),
|
||||
new Fp(10859158614900201063n),
|
||||
new Fp(9763230642109343539n),
|
||||
new Fp(6647722546511515039n),
|
||||
new Fp(909012944955815706n),
|
||||
new Fp(18101204076790399111n),
|
||||
new Fp(11588128829349125809n),
|
||||
new Fp(15863878496612806566n),
|
||||
new Fp(5201119062417750399n),
|
||||
new Fp(176665553780565743n),
|
||||
],
|
||||
];
|
||||
|
||||
const INTERNAL_CONSTANTS: Fp[] = [
|
||||
new Fp(11921381764981422944n),
|
||||
new Fp(10318423381711320787n),
|
||||
new Fp(8291411502347000766n),
|
||||
new Fp(229948027109387563n),
|
||||
new Fp(9152521390190983261n),
|
||||
new Fp(7129306032690285515n),
|
||||
new Fp(15395989607365232011n),
|
||||
new Fp(8641397269074305925n),
|
||||
new Fp(17256848792241043600n),
|
||||
new Fp(6046475228902245682n),
|
||||
new Fp(12041608676381094092n),
|
||||
new Fp(12785542378683951657n),
|
||||
new Fp(14546032085337914034n),
|
||||
new Fp(3304199118235116851n),
|
||||
new Fp(16499627707072547655n),
|
||||
new Fp(10386478025625759321n),
|
||||
new Fp(13475579315436919170n),
|
||||
new Fp(16042710511297532028n),
|
||||
new Fp(1411266850385657080n),
|
||||
new Fp(9024840976168649958n),
|
||||
new Fp(14047056970978379368n),
|
||||
new Fp(838728605080212101n),
|
||||
];
|
||||
|
||||
const MATRIX_DIAG: Fp[] = [
|
||||
new Fp(0xc3b6c08e23ba9300n),
|
||||
new Fp(0xd84b5de94a324fb6n),
|
||||
new Fp(0x0d0c371c5b35b84fn),
|
||||
new Fp(0x7964f570e7188037n),
|
||||
new Fp(0x5daf18bbd996604bn),
|
||||
new Fp(0x6743bc47b9595257n),
|
||||
new Fp(0x5528b9362c59bb70n),
|
||||
new Fp(0xac45e25b7127b68bn),
|
||||
new Fp(0xa2077d7dfbb606b5n),
|
||||
new Fp(0xf3faac6faee378aen),
|
||||
new Fp(0x0c6388b51545e883n),
|
||||
new Fp(0xd27dbb6944917b60n),
|
||||
];
|
||||
|
||||
export function hashToFp5(values: Fp[]): Fp5 {
|
||||
const result = hashNToMNoPad(values, 5);
|
||||
const [c0, c1, c2, c3, c4] = result as [Fp, Fp, Fp, Fp, Fp];
|
||||
return new Fp5([c0, c1, c2, c3, c4]);
|
||||
}
|
||||
|
||||
export function hashTwoToOne(a: Fp[], b: Fp[]): Fp[] {
|
||||
return hashNToMNoPad([...a, ...b], 4);
|
||||
}
|
||||
|
||||
export function hashNToMNoPad(input: Fp[], outputCount: number): Fp[] {
|
||||
const state: Fp[] = Array.from({ length: WIDTH }, () => Fp.ZERO);
|
||||
for (let offset = 0; offset < input.length; offset += RATE) {
|
||||
for (let j = 0; j < RATE && offset + j < input.length; j++) {
|
||||
const current = state[j]!;
|
||||
const value = input[offset + j];
|
||||
if (value === undefined) throw new Error("poseidon input missing element");
|
||||
state[j] = current.add(value);
|
||||
}
|
||||
permute(state);
|
||||
}
|
||||
|
||||
const outputs: Fp[] = [];
|
||||
while (outputs.length < outputCount) {
|
||||
for (let i = 0; i < RATE && outputs.length < outputCount; i++) {
|
||||
outputs.push(state[i]!);
|
||||
}
|
||||
if (outputs.length < outputCount) {
|
||||
permute(state);
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
function permute(state: Fp[]): void {
|
||||
externalLinearLayer(state);
|
||||
fullRounds(state, 0);
|
||||
partialRounds(state);
|
||||
fullRounds(state, ROUNDS_F_HALF);
|
||||
}
|
||||
|
||||
function fullRounds(state: Fp[], startRound: number): void {
|
||||
for (let r = startRound; r < startRound + ROUNDS_F_HALF; r++) {
|
||||
addRoundConstants(state, r);
|
||||
sbox(state);
|
||||
externalLinearLayer(state);
|
||||
}
|
||||
}
|
||||
|
||||
function partialRounds(state: Fp[]): void {
|
||||
for (let r = 0; r < ROUNDS_P; r++) {
|
||||
addInternalConstant(state, r);
|
||||
sboxP(state, 0);
|
||||
internalLinearLayer(state);
|
||||
}
|
||||
}
|
||||
|
||||
function addRoundConstants(state: Fp[], round: number): void {
|
||||
const constants = EXTERNAL_CONSTANTS[round];
|
||||
if (!constants) {
|
||||
throw new Error(`poseidon round constant missing for round ${round}`);
|
||||
}
|
||||
for (let i = 0; i < WIDTH; i++) {
|
||||
state[i] = state[i]!.add(constants[i]!);
|
||||
}
|
||||
}
|
||||
|
||||
function addInternalConstant(state: Fp[], round: number): void {
|
||||
const constant = INTERNAL_CONSTANTS[round];
|
||||
if (!constant) {
|
||||
throw new Error(`poseidon internal constant missing for round ${round}`);
|
||||
}
|
||||
state[0] = state[0]!.add(constant);
|
||||
}
|
||||
|
||||
function sbox(state: Fp[]): void {
|
||||
for (let i = 0; i < WIDTH; i++) {
|
||||
sboxP(state, i);
|
||||
}
|
||||
}
|
||||
|
||||
function sboxP(state: Fp[], index: number): void {
|
||||
const x = state[index];
|
||||
if (!x) throw new Error(`poseidon state missing at index ${index}`);
|
||||
const x2 = x.square();
|
||||
const x3 = x2.mul(x);
|
||||
const x6 = x3.square();
|
||||
state[index] = x6.mul(x);
|
||||
}
|
||||
|
||||
function externalLinearLayer(state: Fp[]): void {
|
||||
for (let block = 0; block < 3; block++) {
|
||||
const base = block * 4;
|
||||
const s0 = state[base]!;
|
||||
const s1 = state[base + 1]!;
|
||||
const s2 = state[base + 2]!;
|
||||
const s3 = state[base + 3]!;
|
||||
|
||||
const t0 = s0.add(s1);
|
||||
const t1 = s2.add(s3);
|
||||
const t2 = t0.add(t1);
|
||||
const t3 = t2.add(s1);
|
||||
const t4 = t2.add(s3);
|
||||
const t5 = s0.double();
|
||||
const t6 = s2.double();
|
||||
|
||||
state[base] = t3.add(t0);
|
||||
state[base + 1] = t6.add(t3);
|
||||
state[base + 2] = t1.add(t4);
|
||||
state[base + 3] = t5.add(t4);
|
||||
}
|
||||
|
||||
const sums: Fp[] = [Fp.ZERO, Fp.ZERO, Fp.ZERO, Fp.ZERO];
|
||||
for (let k = 0; k < 4; k++) {
|
||||
for (let j = 0; j < WIDTH; j += 4) {
|
||||
const currentSum = sums[k];
|
||||
if (currentSum === undefined) throw new Error("poseidon sums missing value");
|
||||
sums[k] = currentSum.add(state[j + k]!);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < WIDTH; i++) {
|
||||
state[i] = state[i]!.add(sums[i % 4]!);
|
||||
}
|
||||
}
|
||||
|
||||
function internalLinearLayer(state: Fp[]): void {
|
||||
let sum = state[0]!;
|
||||
for (let i = 1; i < WIDTH; i++) {
|
||||
sum = sum.add(state[i]!);
|
||||
}
|
||||
for (let i = 0; i < WIDTH; i++) {
|
||||
state[i] = state[i]!.mul(MATRIX_DIAG[i]!).add(sum);
|
||||
}
|
||||
}
|
||||
|
||||
export function hashToQuinticExtension(preimage: Fp[]): Fp5 {
|
||||
return hashToFp5(preimage);
|
||||
}
|
||||
|
||||
export function hashFp5Pair(a: Fp5, b: Fp5): Fp5 {
|
||||
const inputs: Fp[] = [...a.toTuple(), ...b.toTuple()];
|
||||
return hashToQuinticExtension(inputs);
|
||||
}
|
||||
|
||||
export function mergeFp5WithHash(fp5Values: Fp5[]): Fp5 {
|
||||
if (fp5Values.length === 0) return Fp5.ZERO;
|
||||
const first = fp5Values[0];
|
||||
if (!first) throw new Error("mergeFp5WithHash received empty array");
|
||||
let acc = first;
|
||||
for (let i = 1; i < fp5Values.length; i++) {
|
||||
const next = fp5Values[i];
|
||||
if (!next) throw new Error("mergeFp5WithHash encountered undefined value");
|
||||
acc = hashFp5Pair(acc, next);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { Fp } from "./goldilocks";
|
||||
import { Fp5 } from "./goldilocks-fp5";
|
||||
|
||||
const ORDER = BigInt("1067993516717146951041484916571792702745057740581727230159139685185762082554198619328292418486241");
|
||||
const BYTE_LENGTH = 40;
|
||||
const FOUR_BIT_LIMBS = 80;
|
||||
const BIT_LENGTH = 319;
|
||||
|
||||
function modOrder(value: bigint): bigint {
|
||||
let v = value % ORDER;
|
||||
if (v < 0n) v += ORDER;
|
||||
return v;
|
||||
}
|
||||
|
||||
export class Scalar {
|
||||
readonly value: bigint;
|
||||
|
||||
constructor(value: bigint | number) {
|
||||
this.value = modOrder(typeof value === "number" ? BigInt(value) : value);
|
||||
}
|
||||
|
||||
static readonly ZERO = new Scalar(0n);
|
||||
static readonly ONE = new Scalar(1n);
|
||||
|
||||
static fromBytesLE(bytes: Uint8Array): Scalar {
|
||||
if (bytes.length !== BYTE_LENGTH) {
|
||||
throw new Error(`Scalar expects ${BYTE_LENGTH} bytes, got ${bytes.length}`);
|
||||
}
|
||||
let acc = 0n;
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
const byte = bytes[i];
|
||||
if (byte === undefined) throw new Error("Unexpected undefined byte when reading scalar");
|
||||
acc |= BigInt(byte) << BigInt(8 * i);
|
||||
}
|
||||
return new Scalar(acc);
|
||||
}
|
||||
|
||||
toBytesLE(): Uint8Array {
|
||||
let v = this.value;
|
||||
const out = new Uint8Array(BYTE_LENGTH);
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
out[i] = Number(v & 0xffn);
|
||||
v >>= 8n;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
toBigInt(): bigint {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
add(other: Scalar): Scalar {
|
||||
return new Scalar(this.value + other.value);
|
||||
}
|
||||
|
||||
sub(other: Scalar): Scalar {
|
||||
return new Scalar(this.value - other.value);
|
||||
}
|
||||
|
||||
neg(): Scalar {
|
||||
return new Scalar(this.value === 0n ? 0n : ORDER - this.value);
|
||||
}
|
||||
|
||||
mul(other: Scalar): Scalar {
|
||||
return new Scalar(this.value * other.value);
|
||||
}
|
||||
|
||||
square(): Scalar {
|
||||
return this.mul(this);
|
||||
}
|
||||
|
||||
pow(exponent: bigint): Scalar {
|
||||
let result = 1n;
|
||||
let base = this.value;
|
||||
let exp = exponent;
|
||||
while (exp > 0n) {
|
||||
if (exp & 1n) result = modOrder(result * base);
|
||||
base = modOrder(base * base);
|
||||
exp >>= 1n;
|
||||
}
|
||||
return new Scalar(result);
|
||||
}
|
||||
|
||||
isZero(): boolean {
|
||||
return this.value === 0n;
|
||||
}
|
||||
|
||||
equals(other: Scalar): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
clone(): Scalar {
|
||||
return new Scalar(this.value);
|
||||
}
|
||||
|
||||
splitTo4BitLimbs(): Uint8Array {
|
||||
const limbs = new Uint8Array(FOUR_BIT_LIMBS);
|
||||
let tmp = this.value;
|
||||
for (let i = 0; i < FOUR_BIT_LIMBS; i++) {
|
||||
limbs[i] = Number(tmp & 0xfn);
|
||||
tmp >>= 4n;
|
||||
}
|
||||
return limbs;
|
||||
}
|
||||
|
||||
recodeSigned(window: number): Int32Array {
|
||||
const length = Math.ceil(BIT_LENGTH / window);
|
||||
const digits = new Int32Array(length);
|
||||
const twoPowW = 1n << BigInt(window);
|
||||
const twoPowWMinus1 = 1n << BigInt(window - 1);
|
||||
let k = this.value;
|
||||
let i = 0;
|
||||
while (k > 0n && i < length) {
|
||||
if (k & 1n) {
|
||||
let remainder = Number(k % twoPowW);
|
||||
if (remainder >= Number(twoPowWMinus1)) {
|
||||
remainder -= Number(twoPowW);
|
||||
}
|
||||
digits[i] = remainder;
|
||||
k -= BigInt(remainder);
|
||||
}
|
||||
k >>= 1n;
|
||||
i++;
|
||||
}
|
||||
// remaining digits already zero
|
||||
return digits;
|
||||
}
|
||||
|
||||
static random(): Scalar {
|
||||
while (true) {
|
||||
const buf = randomBytes(BYTE_LENGTH);
|
||||
let acc = 0n;
|
||||
for (let i = 0; i < BYTE_LENGTH; i++) {
|
||||
const byte = buf[i];
|
||||
if (byte === undefined) throw new Error("Unexpected undefined byte when sampling scalar");
|
||||
acc |= BigInt(byte) << BigInt(8 * i);
|
||||
}
|
||||
if (acc < ORDER) {
|
||||
return new Scalar(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fromFp5(element: Fp5): Scalar {
|
||||
const coeffs = element.toTuple();
|
||||
const entries = [coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]];
|
||||
let acc = 0n;
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const limb = entries[i];
|
||||
if (!limb) throw new Error("Fp5 tuple missing limb");
|
||||
acc <<= 64n;
|
||||
acc |= limb.toBigInt();
|
||||
}
|
||||
return new Scalar(acc);
|
||||
}
|
||||
}
|
||||
|
||||
export const SCALAR_ORDER = ORDER;
|
||||
@@ -1,64 +0,0 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { Fp } from "./goldilocks";
|
||||
import { Fp5 } from "./goldilocks-fp5";
|
||||
import { hashToQuinticExtension } from "./poseidon2";
|
||||
import { Scalar } from "./scalar";
|
||||
import { ECPoint } from "./curve";
|
||||
|
||||
export interface SchnorrSignature {
|
||||
s: Scalar;
|
||||
e: Scalar;
|
||||
}
|
||||
|
||||
export class LighterPrivateKey {
|
||||
readonly scalar: Scalar;
|
||||
|
||||
constructor(scalar: Scalar) {
|
||||
this.scalar = scalar;
|
||||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): LighterPrivateKey {
|
||||
if (bytes.length !== 40) {
|
||||
throw new Error("Lighter private key must be 40 bytes");
|
||||
}
|
||||
return new LighterPrivateKey(Scalar.fromBytesLE(bytes));
|
||||
}
|
||||
|
||||
static fromHex(hex: string): LighterPrivateKey {
|
||||
const normalized = hex.startsWith("0x") ? hex.slice(2) : hex;
|
||||
if (normalized.length !== 80) {
|
||||
throw new Error("Lighter private key hex must encode 40 bytes");
|
||||
}
|
||||
const bytes = Buffer.from(normalized, "hex");
|
||||
return LighterPrivateKey.fromBytes(bytes);
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.scalar.toBytesLE();
|
||||
}
|
||||
|
||||
publicKey(): Fp5 {
|
||||
const point = ECPoint.generator().mul(this.scalar);
|
||||
return point.encode();
|
||||
}
|
||||
|
||||
signHashedMessage(hashed: Fp5): SchnorrSignature {
|
||||
const k = Scalar.random();
|
||||
const rPoint = ECPoint.generator().mul(k);
|
||||
const rEncoded = rPoint.encode();
|
||||
const preimage: Fp[] = [...rEncoded.toTuple(), ...hashed.toTuple()];
|
||||
const hash = hashToQuinticExtension(preimage);
|
||||
const e = Scalar.fromFp5(hash);
|
||||
const s = k.sub(e.mul(this.scalar));
|
||||
return { s, e };
|
||||
}
|
||||
}
|
||||
|
||||
export function signatureToBytes(sig: SchnorrSignature): Uint8Array {
|
||||
const sBytes = sig.s.toBytesLE();
|
||||
const eBytes = sig.e.toBytesLE();
|
||||
const out = new Uint8Array(80);
|
||||
out.set(sBytes, 0);
|
||||
out.set(eBytes, 40);
|
||||
return out;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Fp, GOLDILOCKS_BYTE_LENGTH } from "./crypto/goldilocks";
|
||||
|
||||
const CHUNK_SIZE = GOLDILOCKS_BYTE_LENGTH;
|
||||
|
||||
export function arrayFromCanonicalLittleEndianBytes(bytes: Uint8Array): Fp[] {
|
||||
if (!bytes.length) return [];
|
||||
const remainder = bytes.length % CHUNK_SIZE;
|
||||
const paddedLength = remainder === 0 ? bytes.length : bytes.length + (CHUNK_SIZE - remainder);
|
||||
const padded = new Uint8Array(paddedLength);
|
||||
padded.set(bytes, 0);
|
||||
const result: Fp[] = [];
|
||||
for (let offset = 0; offset < padded.length; offset += CHUNK_SIZE) {
|
||||
const chunk = padded.subarray(offset, offset + CHUNK_SIZE);
|
||||
result.push(Fp.fromBytesLE(chunk));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Lightweight bridge to the Lighter signer shared library using stdin/stdout JSON RPC."""
|
||||
|
||||
import ctypes
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
class StrOrErr(ctypes.Structure):
|
||||
_fields_ = [("str", ctypes.c_char_p), ("err", ctypes.c_char_p)]
|
||||
|
||||
|
||||
def _resolve_signer_path() -> str:
|
||||
base = os.path.abspath(os.path.dirname(__file__))
|
||||
signers_dir = os.path.join(base, "signers")
|
||||
system = platform.system()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "Darwin":
|
||||
path = os.path.join(signers_dir, "signer-arm64.dylib" if machine == "arm64" else "signer-amd64.dylib")
|
||||
elif system == "Linux":
|
||||
path = os.path.join(signers_dir, "signer-amd64.so")
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {system} {machine}")
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Signer library missing: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def _load_library(path: str) -> ctypes.CDLL:
|
||||
try:
|
||||
return ctypes.CDLL(path)
|
||||
except OSError as exc: # pragma: no cover - runtime environment guard
|
||||
message = str(exc)
|
||||
if platform.system() == "Darwin" and "code signature" in message:
|
||||
subprocess.run(["/usr/bin/xattr", "-d", "com.apple.quarantine", path], check=False, capture_output=True)
|
||||
subprocess.run(["/usr/bin/codesign", "--force", "--sign", "-", path], check=False, capture_output=True)
|
||||
return ctypes.CDLL(path)
|
||||
raise
|
||||
|
||||
|
||||
SIGNER_PATH = _resolve_signer_path()
|
||||
|
||||
try:
|
||||
LIB = _load_library(SIGNER_PATH)
|
||||
except OSError as exc: # pragma: no cover - runtime environment guard
|
||||
print(json.dumps({"id": None, "error": f"failed_to_load_signer:{exc}"}), flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
LIB.CreateClient.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_longlong]
|
||||
LIB.CreateClient.restype = ctypes.c_char_p
|
||||
|
||||
LIB.SwitchAPIKey.argtypes = [ctypes.c_int]
|
||||
LIB.SwitchAPIKey.restype = ctypes.c_char_p
|
||||
|
||||
LIB.SignCreateOrder.argtypes = [
|
||||
ctypes.c_int,
|
||||
ctypes.c_longlong,
|
||||
ctypes.c_longlong,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.c_longlong,
|
||||
ctypes.c_longlong,
|
||||
]
|
||||
LIB.SignCreateOrder.restype = StrOrErr
|
||||
|
||||
LIB.SignCancelOrder.argtypes = [ctypes.c_int, ctypes.c_longlong, ctypes.c_longlong]
|
||||
LIB.SignCancelOrder.restype = StrOrErr
|
||||
|
||||
LIB.SignCancelAllOrders.argtypes = [ctypes.c_int, ctypes.c_longlong, ctypes.c_longlong]
|
||||
LIB.SignCancelAllOrders.restype = StrOrErr
|
||||
|
||||
LIB.CreateAuthToken.argtypes = [ctypes.c_longlong]
|
||||
LIB.CreateAuthToken.restype = StrOrErr
|
||||
|
||||
|
||||
def _unwrap(result: StrOrErr) -> Dict[str, Any]:
|
||||
if result.err:
|
||||
return {"error": ctypes.string_at(result.err).decode("utf-8", errors="replace")}
|
||||
if result.str:
|
||||
return {"result": ctypes.string_at(result.str).decode("utf-8", errors="replace")}
|
||||
return {"result": None}
|
||||
|
||||
|
||||
def _maybe_error(ptr: ctypes.c_char_p) -> Dict[str, Any]:
|
||||
if ptr:
|
||||
return {"error": ctypes.string_at(ptr).decode("utf-8", errors="replace")}
|
||||
return {"result": "ok"}
|
||||
|
||||
|
||||
_INITIALISED_KEYS = set()
|
||||
_CLIENT_CONFIG: Dict[int, Dict[str, Any]] = {}
|
||||
|
||||
|
||||
def _ensure_client(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
api_key_index = int(params["apiKeyIndex"])
|
||||
|
||||
config = _CLIENT_CONFIG.get(api_key_index)
|
||||
if "baseUrl" in params and "privateKey" in params:
|
||||
config = {
|
||||
"baseUrl": params["baseUrl"],
|
||||
"privateKey": params["privateKey"],
|
||||
"chainId": int(params["chainId"]),
|
||||
"accountIndex": int(params["accountIndex"]),
|
||||
}
|
||||
_CLIENT_CONFIG[api_key_index] = config
|
||||
|
||||
if config is None:
|
||||
return {"error": "client_not_initialized"}
|
||||
|
||||
if api_key_index in _INITIALISED_KEYS:
|
||||
return {"result": "ok"}
|
||||
|
||||
err_ptr = LIB.CreateClient(
|
||||
config["baseUrl"].encode("utf-8"),
|
||||
config["privateKey"].encode("utf-8"),
|
||||
ctypes.c_int(int(config["chainId"])),
|
||||
ctypes.c_int(api_key_index),
|
||||
ctypes.c_longlong(int(config["accountIndex"])),
|
||||
)
|
||||
outcome = _maybe_error(err_ptr)
|
||||
if "error" in outcome:
|
||||
return outcome
|
||||
|
||||
_INITIALISED_KEYS.add(api_key_index)
|
||||
return {"result": "ok"}
|
||||
|
||||
|
||||
def _switch_api_key(api_key_index: int) -> Dict[str, Any]:
|
||||
err_ptr = LIB.SwitchAPIKey(ctypes.c_int(api_key_index))
|
||||
return _maybe_error(err_ptr)
|
||||
|
||||
|
||||
def handle_create_client(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return _ensure_client(params)
|
||||
|
||||
|
||||
def handle_sign_create_order(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
ensure = _ensure_client(params)
|
||||
if "error" in ensure:
|
||||
return ensure
|
||||
|
||||
api_key_index = int(params["apiKeyIndex"])
|
||||
switched = _switch_api_key(api_key_index)
|
||||
if "error" in switched:
|
||||
return switched
|
||||
|
||||
expiry = int(params["orderExpiry"])
|
||||
result = LIB.SignCreateOrder(
|
||||
ctypes.c_int(int(params["marketIndex"])),
|
||||
ctypes.c_longlong(int(params["clientOrderIndex"])),
|
||||
ctypes.c_longlong(int(params["baseAmount"])),
|
||||
ctypes.c_int(int(params["price"])),
|
||||
ctypes.c_int(int(params["isAsk"])),
|
||||
ctypes.c_int(int(params["orderType"])),
|
||||
ctypes.c_int(int(params["timeInForce"])),
|
||||
ctypes.c_int(int(params["reduceOnly"])),
|
||||
ctypes.c_int(int(params["triggerPrice"])),
|
||||
ctypes.c_longlong(expiry),
|
||||
ctypes.c_longlong(int(params["nonce"])),
|
||||
)
|
||||
return _unwrap(result)
|
||||
|
||||
|
||||
def handle_sign_cancel_order(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
ensure = _ensure_client(params)
|
||||
if "error" in ensure:
|
||||
return ensure
|
||||
|
||||
api_key_index = int(params["apiKeyIndex"])
|
||||
switched = _switch_api_key(api_key_index)
|
||||
if "error" in switched:
|
||||
return switched
|
||||
|
||||
result = LIB.SignCancelOrder(
|
||||
ctypes.c_int(int(params["marketIndex"])),
|
||||
ctypes.c_longlong(int(params["orderIndex"])),
|
||||
ctypes.c_longlong(int(params["nonce"])),
|
||||
)
|
||||
return _unwrap(result)
|
||||
|
||||
|
||||
def handle_sign_cancel_all(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
ensure = _ensure_client(params)
|
||||
if "error" in ensure:
|
||||
return ensure
|
||||
|
||||
api_key_index = int(params["apiKeyIndex"])
|
||||
switched = _switch_api_key(api_key_index)
|
||||
if "error" in switched:
|
||||
return switched
|
||||
|
||||
result = LIB.SignCancelAllOrders(
|
||||
ctypes.c_int(int(params["timeInForce"])),
|
||||
ctypes.c_longlong(int(params["scheduledTime"])),
|
||||
ctypes.c_longlong(int(params["nonce"])),
|
||||
)
|
||||
return _unwrap(result)
|
||||
|
||||
|
||||
def handle_create_auth_token(params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
ensure = _ensure_client(params)
|
||||
if "error" in ensure:
|
||||
return ensure
|
||||
|
||||
api_key_index = int(params["apiKeyIndex"])
|
||||
switched = _switch_api_key(api_key_index)
|
||||
if "error" in switched:
|
||||
return switched
|
||||
|
||||
result = LIB.CreateAuthToken(ctypes.c_longlong(int(params["deadlineMs"])))
|
||||
return _unwrap(result)
|
||||
|
||||
|
||||
HANDLERS = {
|
||||
"create_client": handle_create_client,
|
||||
"sign_create_order": handle_sign_create_order,
|
||||
"sign_cancel_order": handle_sign_cancel_order,
|
||||
"sign_cancel_all": handle_sign_cancel_all,
|
||||
"create_auth_token": handle_create_auth_token,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
request = json.loads(line)
|
||||
except json.JSONDecodeError as exc: # pragma: no cover - defensive
|
||||
print(json.dumps({"id": None, "error": f"invalid_json:{exc}"}), flush=True)
|
||||
continue
|
||||
|
||||
req_id = request.get("id")
|
||||
method = request.get("method")
|
||||
params = request.get("params", {})
|
||||
|
||||
handler = HANDLERS.get(method)
|
||||
if not handler:
|
||||
response = {"id": req_id, "error": f"unknown_method:{method}"}
|
||||
else:
|
||||
try:
|
||||
outcome = handler(params)
|
||||
outcome.setdefault("id", req_id)
|
||||
response = outcome
|
||||
except Exception as exc: # pragma: no cover - safety net
|
||||
response = {"id": req_id, "error": f"exception:{exc}"}
|
||||
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -130,7 +130,7 @@ class PythonSignerBridge {
|
||||
|
||||
function resolveScriptPath(): string {
|
||||
const current = path.dirname(fileURLToPath(import.meta.url));
|
||||
return path.resolve(current, "../../..", "scripts", "lighter_signer_bridge.py");
|
||||
return path.resolve(current, "./lighter_signer_bridge.py");
|
||||
}
|
||||
|
||||
export class LighterSigner {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user