Files
openpgpjs/src/crypto/biginteger.ts
larabr 97d341a11f Linter: enforce JSDoc @access directive
To make sure only user-facing entities are included in the docs,
since access is public by default.

NB: the top-level access directive seems to work to hide index entrypoint files,
but in other cases (e.g. s2k submodules), exported functions may need to
manually be marked as private.

Also, the 'initialCommentsOnly' rule sometimes reports false positives
in case of multiple comment blocks separated by new lines. The solution
is to remove the new lines.
2025-11-05 12:01:02 +01:00

222 lines
5.0 KiB
TypeScript

/**
* @module biginteger
* @access private
*/
// Operations are not constant time, but we try and limit timing leakage where we can
const _0n = BigInt(0);
const _1n = BigInt(1);
export function uint8ArrayToBigInt(bytes: Uint8Array) {
const hexAlphabet = '0123456789ABCDEF';
let s = '';
bytes.forEach(v => {
s += hexAlphabet[v >> 4] + hexAlphabet[v & 15];
});
return BigInt('0x0' + s);
}
export function mod(a: bigint, m: bigint) {
const reduced = a % m;
return reduced < _0n ? reduced + m : reduced;
}
/**
* Compute modular exponentiation using square and multiply
* @param {BigInt} a - Base
* @param {BigInt} e - Exponent
* @param {BigInt} n - Modulo
* @returns {BigInt} b ** e mod n.
*/
export function modExp(b: bigint, e: bigint, n: bigint) {
if (n === _0n) throw Error('Modulo cannot be zero');
if (n === _1n) return BigInt(0);
if (e < _0n) throw Error('Unsopported negative exponent');
let exp = e;
let x = b;
x %= n;
let r = BigInt(1);
while (exp > _0n) {
const lsb = exp & _1n;
exp >>= _1n; // e / 2
// Always compute multiplication step, to reduce timing leakage
const rx = (r * x) % n;
// Update r only if lsb is 1 (odd exponent)
r = lsb ? rx : r;
x = (x * x) % n; // Square
}
return r;
}
function abs(x: bigint) {
return x >= _0n ? x : -x;
}
/**
* Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf)
* Given a and b, compute (x, y) such that ax + by = gdc(a, b).
* Negative numbers are also supported.
* @param {BigInt} a - First operand
* @param {BigInt} b - Second operand
* @returns {{ gcd, x, y: bigint }}
*/
function _egcd(aInput: bigint, bInput: bigint) {
let x = BigInt(0);
let y = BigInt(1);
let xPrev = BigInt(1);
let yPrev = BigInt(0);
// Deal with negative numbers: run algo over absolute values,
// and "move" the sign to the returned x and/or y.
// See https://math.stackexchange.com/questions/37806/extended-euclidean-algorithm-with-negative-numbers
let a = abs(aInput);
let b = abs(bInput);
const aNegated = aInput < _0n;
const bNegated = bInput < _0n;
while (b !== _0n) {
const q = a / b;
let tmp = x;
x = xPrev - q * x;
xPrev = tmp;
tmp = y;
y = yPrev - q * y;
yPrev = tmp;
tmp = b;
b = a % b;
a = tmp;
}
return {
x: aNegated ? -xPrev : xPrev,
y: bNegated ? -yPrev : yPrev,
gcd: a
};
}
/**
* Compute the inverse of `a` modulo `n`
* Note: `a` and and `n` must be relatively prime
* @param {BigInt} a
* @param {BigInt} n - Modulo
* @returns {BigInt} x such that a*x = 1 mod n
* @throws {Error} if the inverse does not exist
*/
export function modInv(a: bigint, n: bigint) {
const { gcd, x } = _egcd(a, n);
if (gcd !== _1n) {
throw new Error('Inverse does not exist');
}
return mod(x + n, n);
}
/**
* Compute greatest common divisor between this and n
* @param {BigInt} aInput - Operand
* @param {BigInt} bInput - Operand
* @returns {BigInt} gcd
*/
export function gcd(aInput: bigint, bInput: bigint) {
let a = aInput;
let b = bInput;
while (b !== _0n) {
const tmp = b;
b = a % b;
a = tmp;
}
return a;
}
/**
* Get this value as an exact Number (max 53 bits)
* Fails if this value is too large
* @returns {Number}
*/
export function bigIntToNumber(x: bigint) {
const number = Number(x);
if (number > Number.MAX_SAFE_INTEGER) {
// We throw and error to conform with the bn.js implementation
throw new Error('Number can only safely store up to 53 bits');
}
return number;
}
/**
* Get value of i-th bit
* @param {BigInt} x
* @param {Number} i - Bit index
* @returns {Number} Bit value.
*/
export function getBit(x:bigint, i: number) {
const bit = (x >> BigInt(i)) & _1n;
return bit === _0n ? 0 : 1;
}
/**
* Compute bit length
*/
export function bitLength(x: bigint) {
// -1n >> -1n is -1n
// 1n >> 1n is 0n
const target = x < _0n ? BigInt(-1) : _0n;
let bitlen = 1;
let tmp = x;
while ((tmp >>= _1n) !== target) {
bitlen++;
}
return bitlen;
}
/**
* Compute byte length
*/
export function byteLength(x: bigint) {
const target = x < _0n ? BigInt(-1) : _0n;
const _8n = BigInt(8);
let len = 1;
let tmp = x;
while ((tmp >>= _8n) !== target) {
len++;
}
return len;
}
/**
* Get Uint8Array representation of this number
* @param {String} endian - Endianess of output array (defaults to 'be')
* @param {Number} length - Of output array
* @returns {Uint8Array}
*/
export function bigIntToUint8Array(x: bigint, endian = 'be', length: number) {
// we get and parse the hex string (https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/)
// this is faster than shift+mod iterations
let hex = x.toString(16);
if (hex.length % 2 === 1) {
hex = '0' + hex;
}
const rawLength = hex.length / 2;
const bytes = new Uint8Array(length || rawLength);
// parse hex
const offset = length ? length - rawLength : 0;
let i = 0;
while (i < rawLength) {
bytes[i + offset] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
i++;
}
if (endian !== 'be') {
bytes.reverse();
}
return bytes;
}