mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-11-29 08:28:43 +00:00
217 lines
5.1 KiB
TypeScript
217 lines
5.1 KiB
TypeScript
// 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;
|
|
// eslint-disable-next-line no-cond-assign
|
|
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;
|
|
// eslint-disable-next-line no-cond-assign
|
|
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;
|
|
}
|