@symbioticfi/relay-stats-ts
Version:
TypeScript library for deriving validator sets from Symbiotic network contracts
151 lines • 4.71 kB
JavaScript
import { keccak256 } from 'viem';
// Minimal BN254 (altbn128) helpers for G1 in affine coordinates.
// This is a lightweight implementation sufficient for building extraData only.
const FP_MODULUS = BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47');
// (p+1)/4 used for Tonelli-Shanks on BN254 (matches Go implementation)
const SQRT_EXPONENT = BigInt('0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52');
function mod(a, m = FP_MODULUS) {
const r = a % m;
return r >= 0n ? r : r + m;
}
// reserved helpers (kept simple for clarity)
function modMul(a, b) {
return mod(a * b);
}
function modPow(base, exp) {
let result = 1n;
let b = mod(base);
let e = exp;
while (e > 0n) {
if (e & 1n)
result = modMul(result, b);
b = modMul(b, b);
e >>= 1n;
}
return result;
}
function modInv(a) {
// a^(p-2) mod p for prime field
return modPow(mod(a), FP_MODULUS - 2n);
}
function fromHex(hex) {
const h = hex.startsWith('0x') ? hex.slice(2) : hex;
if (h.length % 2 !== 0)
throw new Error('Invalid hex');
return new Uint8Array(Buffer.from(h, 'hex'));
}
function toHex(bytes) {
return ('0x' + Buffer.from(bytes).toString('hex'));
}
function bytesToBigIntBE(bytes) {
// Preserve leading zeros by padding to the correct length
const hex = Buffer.from(bytes).toString('hex');
return BigInt('0x' + hex);
}
function bigIntToBytes32BE(x) {
const out = new Uint8Array(32);
const hex = x.toString(16).padStart(64, '0');
out.set(Buffer.from(hex, 'hex'));
return out;
}
function isInfinity(p) {
return p === null;
}
// equality not required by current usage
export function findYFromX(x) {
// Go: Calculate beta = x^3 + 3 mod p
const beta = mod(modPow(mod(x), 3n) + 3n);
// Go: Calculate y = beta^((p+1)/4) mod p
return modPow(beta, SQRT_EXPONENT);
}
export function compressG1FromXY(x, y) {
const xMod = mod(x);
const yMod = mod(y);
const derivedY = findYFromX(xMod);
const flag = yMod === derivedY ? 0n : 1n;
const compressed = 2n * xMod + flag;
return toHex(bigIntToBytes32BE(compressed));
}
export function parseG1Uncompressed(raw) {
const bytes = fromHex(raw);
if (bytes.length < 64)
throw new Error('Expected 64-byte uncompressed G1');
// Extract X and Y coordinates directly from hex string to preserve leading zeros
const hex = raw.startsWith('0x') ? raw.slice(2) : raw;
const xHex = hex.slice(0, 64);
const yHex = hex.slice(64, 128);
const x = BigInt('0x' + xHex);
const y = BigInt('0x' + yHex.padStart(64, '0'));
return { x: mod(x), y: mod(y) };
}
// negation not required by current usage
function pointDouble(p) {
if (isInfinity(p))
return p;
if (p.y === 0n)
return null;
const lambda = modMul(3n * modMul(p.x, p.x), modInv(2n * p.y));
const xr = mod(lambda * lambda - 2n * p.x);
const yr = mod(lambda * (p.x - xr) - p.y);
return { x: xr, y: yr };
}
export function pointAdd(a, b) {
if (isInfinity(a))
return b;
if (isInfinity(b))
return a;
if (a.x === b.x) {
if (mod(a.y + b.y) === 0n)
return null;
return pointDouble(a);
}
const lambda = modMul(b.y - a.y, modInv(b.x - a.x));
const xr = mod(lambda * lambda - a.x - b.x);
const yr = mod(lambda * (a.x - xr) - a.y);
return { x: xr, y: yr };
}
export function aggregateG1(keys) {
let acc = null;
for (const k of keys) {
const p = parseKeyToPoint(k);
acc = pointAdd(acc, p);
}
return acc;
}
export function compressAggregatedG1(keys) {
const p = aggregateG1(keys);
if (p === null)
return ('0x' + '00'.repeat(32));
return compressG1FromXY(p.x, p.y);
}
export function compressRawG1(raw) {
const p = parseKeyToPoint(raw);
if (p === null)
return ('0x' + '00'.repeat(32));
return compressG1FromXY(p.x, p.y);
}
export function keccak(bytes) {
return keccak256(toHex(bytes));
}
function parseCompressedToPoint(raw) {
const bytes = fromHex(raw);
if (bytes.length !== 32)
throw new Error('Expected 32-byte compressed G1');
const v = bytesToBigIntBE(bytes);
const x = v >> 1n;
const flag = v & 1n;
let y = findYFromX(x);
if (flag === 1n) {
y = mod(-y);
}
return { x: mod(x), y };
}
export function parseKeyToPoint(raw) {
const bytes = fromHex(raw);
if (bytes.length >= 64)
return parseG1Uncompressed(raw);
if (bytes.length === 32)
return parseCompressedToPoint(raw);
throw new Error('Unsupported G1 key length');
}
//# sourceMappingURL=bls_bn254.js.map