Files
ritmex-bot/src/exchanges/lighter/crypto/scalar.ts
T

160 lines
3.9 KiB
TypeScript

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;