diff --git a/scripts/lighter_signer_bridge.py b/scripts/lighter_signer_bridge.py index 78607a6..dba14eb 100755 --- a/scripts/lighter_signer_bridge.py +++ b/scripts/lighter_signer_bridge.py @@ -17,7 +17,8 @@ class StrOrErr(ctypes.Structure): def _resolve_signer_path() -> str: base = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - signers_dir = os.path.join(base, "docs", "lighter", "lighter-python-main", "lighter", "signers") + # Resolve to in-repo module location: src/exchanges/lighter/signers + signers_dir = os.path.join(base, "src", "exchanges", "lighter", "signers") system = platform.system() machine = platform.machine().lower() diff --git a/src/exchanges/lighter/crypto/curve.ts b/src/exchanges/lighter/crypto/curve.ts deleted file mode 100644 index 5528eaf..0000000 --- a/src/exchanges/lighter/crypto/curve.ts +++ /dev/null @@ -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); -} diff --git a/src/exchanges/lighter/crypto/goldilocks-fp5.ts b/src/exchanges/lighter/crypto/goldilocks-fp5.ts deleted file mode 100644 index d0c4cfe..0000000 --- a/src/exchanges/lighter/crypto/goldilocks-fp5.ts +++ /dev/null @@ -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): 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(); -} diff --git a/src/exchanges/lighter/crypto/goldilocks.ts b/src/exchanges/lighter/crypto/goldilocks.ts deleted file mode 100644 index 43bd7bf..0000000 --- a/src/exchanges/lighter/crypto/goldilocks.ts +++ /dev/null @@ -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(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; diff --git a/src/exchanges/lighter/crypto/poseidon2.ts b/src/exchanges/lighter/crypto/poseidon2.ts deleted file mode 100644 index 0519cf3..0000000 --- a/src/exchanges/lighter/crypto/poseidon2.ts +++ /dev/null @@ -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; -} diff --git a/src/exchanges/lighter/crypto/scalar.ts b/src/exchanges/lighter/crypto/scalar.ts deleted file mode 100644 index 88985ff..0000000 --- a/src/exchanges/lighter/crypto/scalar.ts +++ /dev/null @@ -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; diff --git a/src/exchanges/lighter/crypto/schnorr.ts b/src/exchanges/lighter/crypto/schnorr.ts deleted file mode 100644 index 1395ef2..0000000 --- a/src/exchanges/lighter/crypto/schnorr.ts +++ /dev/null @@ -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; -} diff --git a/src/exchanges/lighter/field-array.ts b/src/exchanges/lighter/field-array.ts deleted file mode 100644 index 12aa21a..0000000 --- a/src/exchanges/lighter/field-array.ts +++ /dev/null @@ -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; -} diff --git a/src/exchanges/lighter/lighter_signer_bridge.py b/src/exchanges/lighter/lighter_signer_bridge.py new file mode 100755 index 0000000..6e5844a --- /dev/null +++ b/src/exchanges/lighter/lighter_signer_bridge.py @@ -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() diff --git a/src/exchanges/lighter/signer.ts b/src/exchanges/lighter/signer.ts index ed91d29..9162381 100644 --- a/src/exchanges/lighter/signer.ts +++ b/src/exchanges/lighter/signer.ts @@ -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 { diff --git a/src/exchanges/lighter/signers/signer-amd64.so b/src/exchanges/lighter/signers/signer-amd64.so new file mode 100644 index 0000000..8cba2cd Binary files /dev/null and b/src/exchanges/lighter/signers/signer-amd64.so differ diff --git a/src/exchanges/lighter/signers/signer-arm64.dylib b/src/exchanges/lighter/signers/signer-arm64.dylib new file mode 100644 index 0000000..1ef58f7 Binary files /dev/null and b/src/exchanges/lighter/signers/signer-arm64.dylib differ