From b7cd6eed997f8af779e56c6960a80facf28da36d Mon Sep 17 00:00:00 2001 From: discountry Date: Fri, 3 Oct 2025 01:51:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=20Lighter=20?= =?UTF-8?q?=E7=AD=96=E7=95=A5=EF=BC=8C=E6=9B=B4=E6=96=B0=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E6=A1=A5=E6=8E=A5=E8=B7=AF=E5=BE=84=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E6=96=87=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84=E7=AD=BE=E5=90=8D=E5=BA=93?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lighter_signer_bridge.py | 3 +- src/exchanges/lighter/crypto/curve.ts | 144 ------ .../lighter/crypto/goldilocks-fp5.ts | 460 ------------------ src/exchanges/lighter/crypto/goldilocks.ts | 179 ------- src/exchanges/lighter/crypto/poseidon2.ts | 320 ------------ src/exchanges/lighter/crypto/scalar.ts | 159 ------ src/exchanges/lighter/crypto/schnorr.ts | 64 --- src/exchanges/lighter/field-array.ts | 17 - .../lighter/lighter_signer_bridge.py | 265 ++++++++++ src/exchanges/lighter/signer.ts | 2 +- src/exchanges/lighter/signers/signer-amd64.so | Bin 0 -> 9229520 bytes .../lighter/signers/signer-arm64.dylib | Bin 0 -> 6213008 bytes 12 files changed, 268 insertions(+), 1345 deletions(-) delete mode 100644 src/exchanges/lighter/crypto/curve.ts delete mode 100644 src/exchanges/lighter/crypto/goldilocks-fp5.ts delete mode 100644 src/exchanges/lighter/crypto/goldilocks.ts delete mode 100644 src/exchanges/lighter/crypto/poseidon2.ts delete mode 100644 src/exchanges/lighter/crypto/scalar.ts delete mode 100644 src/exchanges/lighter/crypto/schnorr.ts delete mode 100644 src/exchanges/lighter/field-array.ts create mode 100755 src/exchanges/lighter/lighter_signer_bridge.py create mode 100644 src/exchanges/lighter/signers/signer-amd64.so create mode 100644 src/exchanges/lighter/signers/signer-arm64.dylib 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 0000000000000000000000000000000000000000..8cba2cd68d6dc167e1218a33e0ab8de34a7a7859 GIT binary patch literal 9229520 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