mtcute/packages/core/src/utils/bigint-utils.ts

230 lines
5.5 KiB
TypeScript

import { bufferToReversed, randomBytes } from './buffer-utils.js'
/**
* Get the minimum number of bits required to represent a number
*/
export function bigIntBitLength(n: bigint) {
// not the fastest way, but at least not .toString(2) and not too complex
// taken from: https://stackoverflow.com/a/76616288/22656950
const i = (n.toString(16).length - 1) * 4
return i + 32 - Math.clz32(Number(n >> BigInt(i)))
}
/**
* Convert a big integer to a buffer
*
* @param value Value to convert
* @param length Length of the resulting buffer (by default it's the minimum required)
* @param le Whether to use little-endian encoding
*/
export function bigIntToBuffer(value: bigint, length = 0, le = false): Uint8Array {
const bits = bigIntBitLength(value)
const bytes = Math.ceil(bits / 8)
if (length !== 0 && bytes > length) {
throw new Error('Value out of bounds')
}
if (length === 0) length = bytes
const buf = new ArrayBuffer(length)
const u8 = new Uint8Array(buf)
const unaligned = length % 8
const dv = new DataView(buf, 0, length - unaligned)
// it is faster to work with 64-bit words than with bytes directly
for (let i = 0; i < dv.byteLength; i += 8) {
dv.setBigUint64(i, value & 0xffffffffffffffffn, true)
value >>= 64n
}
if (unaligned > 0) {
for (let i = length - unaligned; i < length; i++) {
u8[i] = Number(value & 0xffn)
value >>= 8n
}
}
if (!le) u8.reverse()
return u8
}
/**
* Convert a buffer to a big integer
*
* @param buffer Buffer to convert
* @param le Whether to use little-endian encoding
*/
export function bufferToBigInt(buffer: Uint8Array, le = false): bigint {
if (le) buffer = bufferToReversed(buffer)
const unaligned = buffer.length % 8
const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength - unaligned)
let res = 0n
// it is faster to work with 64-bit words than with bytes directly
for (let i = 0; i < dv.byteLength; i += 8) {
res = (res << 64n) | BigInt(dv.getBigUint64(i, false))
}
if (unaligned > 0) {
for (let i = buffer.length - unaligned; i < buffer.length; i++) {
res = (res << 8n) | BigInt(buffer[i])
}
}
return res
}
/**
* Generate a random big integer of the given size (in bytes)
* @param size Size in bytes
*/
export function randomBigInt(size: number): bigint {
return bufferToBigInt(randomBytes(size))
}
/**
* Generate a random big integer of the given size (in bits)
* @param bits
*/
export function randomBigIntBits(bits: number): bigint {
let num = randomBigInt(Math.ceil(bits / 8))
const bitLength = bigIntBitLength(num)
if (bitLength > bits) {
const toTrim = bitLength - bits
num >>= BigInt(toTrim)
}
return num
}
/**
* Generate a random big integer in the range [min, max)
*
* @param max Maximum value (exclusive)
* @param min Minimum value (inclusive)
*/
export function randomBigIntInRange(max: bigint, min = 1n): bigint {
const interval = max - min
if (interval < 0n) throw new Error('expected min < max')
const byteSize = bigIntBitLength(interval) / 8
let result = randomBigInt(byteSize)
while (result > interval) result -= interval
return min + result
}
/**
* Compute the multiplicity of 2 in the prime factorization of n
* @param n
*/
export function twoMultiplicity(n: bigint): bigint {
if (n === 0n) return 0n
let m = 0n
let pow = 1n
while (true) {
if ((n & pow) !== 0n) return m
m += 1n
pow <<= 1n
}
}
export function bigIntMin(a: bigint, b: bigint): bigint {
return a < b ? a : b
}
export function bigIntAbs(a: bigint): bigint {
return a < 0n ? -a : a
}
export function bigIntGcd(a: bigint, b: bigint): bigint {
// using euclidean algorithm is fast enough on smaller numbers
// https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations
while (b !== 0n) {
const t = b
b = a % b
a = t
}
return a
}
export function bigIntModPow(base: bigint, exp: bigint, mod: bigint): bigint {
// using the binary method is good enough for our use case
// https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
base %= mod
let result = 1n
while (exp > 0n) {
if (exp % 2n === 1n) {
result = (result * base) % mod
}
exp >>= 1n
base = base ** 2n % mod
}
return result
}
// below code is based on https://github.com/juanelas/bigint-mod-arith, MIT license
function eGcd(a: bigint, b: bigint): [bigint, bigint, bigint] {
let x = 0n
let y = 1n
let u = 1n
let v = 0n
while (a !== 0n) {
const q = b / a
const r: bigint = b % a
const m = x - u * q
const n = y - v * q
b = a
a = r
x = u
y = v
u = m
v = n
}
return [b, x, y]
}
function toZn(a: number | bigint, n: number | bigint): bigint {
if (typeof a === 'number') a = BigInt(a)
if (typeof n === 'number') n = BigInt(n)
if (n <= 0n) {
throw new RangeError('n must be > 0')
}
const aZn = a % n
return aZn < 0n ? aZn + n : aZn
}
export function bigIntModInv(a: bigint, n: bigint): bigint {
const [g, x] = eGcd(toZn(a, n), n)
if (g !== 1n) {
throw new RangeError(`${a.toString()} does not have inverse modulo ${n.toString()}`) // modular inverse does not exist
} else {
return toZn(x, n)
}
}