mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-03-30 15:08:32 +00:00
Drop BigInteger class, use standalone helpers
This commit is contained in:
parent
90495522f7
commit
cf0285add5
13
package-lock.json
generated
13
package-lock.json
generated
@ -27,6 +27,7 @@
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"argon2id": "^1.0.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"bn.js": "^5.2.1",
|
||||
"c8": "^8.0.1",
|
||||
"chai": "^4.4.1",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
@ -2141,6 +2142,12 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
@ -9783,6 +9790,12 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
|
@ -80,6 +80,7 @@
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"argon2id": "^1.0.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"bn.js": "^5.2.1",
|
||||
"c8": "^8.0.1",
|
||||
"chai": "^4.4.1",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
|
@ -64,7 +64,9 @@ export default Object.assign([
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript(),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' } // to avoid js files being overwritten
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
@ -89,7 +91,9 @@ export default Object.assign([
|
||||
resolve({
|
||||
exportConditions: ['node'] // needed for resolution of noble-curves import of '@noble/crypto' in Node 16 and 18
|
||||
}),
|
||||
typescript(),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' }
|
||||
}),
|
||||
commonjs(),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`
|
||||
@ -109,7 +113,9 @@ export default Object.assign([
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript(),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/lightweight/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
@ -136,7 +142,9 @@ export default Object.assign([
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript(),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './test/lib/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies),
|
||||
requireReturnsDefault: 'preferred'
|
||||
|
@ -1,499 +0,0 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* BigInteger implementation of basic operations
|
||||
* that wraps the native BigInt library.
|
||||
* Operations are not constant time,
|
||||
* but we try and limit timing leakage where we can
|
||||
*/
|
||||
export default class BigInteger {
|
||||
private value: bigint;
|
||||
|
||||
/**
|
||||
* Get a BigInteger (input must be big endian for strings and arrays)
|
||||
* @param {Number|String|Uint8Array} n - Value to convert
|
||||
* @throws {Error} on null or undefined input
|
||||
*/
|
||||
constructor(n: Uint8Array | string | number | bigint) {
|
||||
if (n === undefined) {
|
||||
throw new Error('Invalid BigInteger input');
|
||||
}
|
||||
|
||||
if (n instanceof Uint8Array) {
|
||||
const bytes = n;
|
||||
const hexAlphabet = '0123456789ABCDEF';
|
||||
let s = '';
|
||||
bytes.forEach(v => {
|
||||
s += hexAlphabet[v >> 4] + hexAlphabet[v & 15];
|
||||
});
|
||||
this.value = BigInt('0x0' + s);
|
||||
} else {
|
||||
this.value = BigInt(n);
|
||||
}
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new BigInteger(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger increment in place
|
||||
*/
|
||||
iinc() {
|
||||
this.value++;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger increment
|
||||
* @returns {BigInteger} this + 1.
|
||||
*/
|
||||
inc() {
|
||||
return this.clone().iinc();
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger decrement in place
|
||||
*/
|
||||
idec() {
|
||||
this.value--;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger decrement
|
||||
* @returns {BigInteger} this - 1.
|
||||
*/
|
||||
dec() {
|
||||
return this.clone().idec();
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger addition in place
|
||||
* @param {BigInteger} x - Value to add
|
||||
*/
|
||||
iadd(x: BigInteger) {
|
||||
this.value += x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger addition
|
||||
* @param {BigInteger} x - Value to add
|
||||
* @returns {BigInteger} this + x.
|
||||
*/
|
||||
add(x: BigInteger) {
|
||||
return this.clone().iadd(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger subtraction in place
|
||||
* @param {BigInteger} x - Value to subtract
|
||||
*/
|
||||
isub(x: BigInteger) {
|
||||
this.value -= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger subtraction
|
||||
* @param {BigInteger} x - Value to subtract
|
||||
* @returns {BigInteger} this - x.
|
||||
*/
|
||||
sub(x: BigInteger) {
|
||||
return this.clone().isub(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger multiplication in place
|
||||
* @param {BigInteger} x - Value to multiply
|
||||
*/
|
||||
imul(x: BigInteger) {
|
||||
this.value *= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger multiplication
|
||||
* @param {BigInteger} x - Value to multiply
|
||||
* @returns {BigInteger} this * x.
|
||||
*/
|
||||
mul(x: BigInteger) {
|
||||
return this.clone().imul(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute value modulo m, in place
|
||||
* @param {BigInteger} m - Modulo
|
||||
*/
|
||||
imod(m: BigInteger) {
|
||||
this.value %= m.value;
|
||||
if (this.isNegative()) {
|
||||
this.iadd(m);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute value modulo m
|
||||
* @param {BigInteger} m - Modulo
|
||||
* @returns {BigInteger} this mod m.
|
||||
*/
|
||||
mod(m: BigInteger) {
|
||||
return this.clone().imod(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute modular exponentiation using square and multiply
|
||||
* @param {BigInteger} e - Exponent
|
||||
* @param {BigInteger} n - Modulo
|
||||
* @returns {BigInteger} this ** e mod n.
|
||||
*/
|
||||
modExp(e: BigInteger, n: BigInteger) {
|
||||
if (n.isZero()) throw Error('Modulo cannot be zero');
|
||||
if (n.isOne()) return new BigInteger(0);
|
||||
if (e.isNegative()) throw Error('Unsopported negative exponent');
|
||||
|
||||
let exp = e.value;
|
||||
let x = this.value;
|
||||
|
||||
x %= n.value;
|
||||
let r = BigInt(1);
|
||||
while (exp > BigInt(0)) {
|
||||
const lsb = exp & BigInt(1);
|
||||
exp >>= BigInt(1); // e / 2
|
||||
// Always compute multiplication step, to reduce timing leakage
|
||||
const rx = (r * x) % n.value;
|
||||
// Update r only if lsb is 1 (odd exponent)
|
||||
r = lsb ? rx : r;
|
||||
x = (x * x) % n.value; // Square
|
||||
}
|
||||
return new BigInteger(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the inverse of this value modulo n
|
||||
* Note: this and and n must be relatively prime
|
||||
* @param {BigInteger} n - Modulo
|
||||
* @returns {BigInteger} x such that this*x = 1 mod n
|
||||
* @throws {Error} if the inverse does not exist
|
||||
*/
|
||||
modInv(n: BigInteger) {
|
||||
const { gcd, x } = this._egcd(n);
|
||||
if (!gcd.isOne()) {
|
||||
throw new Error('Inverse does not exist');
|
||||
}
|
||||
return x.add(n).mod(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger division, in place
|
||||
* @param {BigInteger} n - Value to divide
|
||||
*/
|
||||
idiv(n: BigInteger) {
|
||||
this.value /= n.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* BigInteger division
|
||||
* @param {BigInteger} n - Value to divide
|
||||
* @returns {BigInteger} this divded by n.
|
||||
*/
|
||||
div(n: BigInteger) {
|
||||
return this.clone().idiv(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf)
|
||||
* Given a = this and b, compute (x, y) such that ax + by = gdc(a, b).
|
||||
* Negative numbers are also supported.
|
||||
* @param {BigInteger} b - Second operand
|
||||
* @returns {{ gcd, x, y: BigInteger }}
|
||||
*/
|
||||
private _egcd(bInput: BigInteger) {
|
||||
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 = this.abs().value;
|
||||
let b = bInput.abs().value;
|
||||
const aNegated = this.isNegative();
|
||||
const bNegated = bInput.isNegative();
|
||||
|
||||
while (b !== BigInt(0)) {
|
||||
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: new BigInteger(aNegated ? -xPrev : xPrev),
|
||||
y: new BigInteger(bNegated ? -yPrev : yPrev),
|
||||
gcd: new BigInteger(a)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute greatest common divisor between this and n
|
||||
* @param {BigInteger} b - Operand
|
||||
* @returns {BigInteger} gcd
|
||||
*/
|
||||
gcd(bInput: BigInteger) {
|
||||
let a = this.value;
|
||||
let b = bInput.value;
|
||||
while (b !== BigInt(0)) {
|
||||
const tmp = b;
|
||||
b = a % b;
|
||||
a = tmp;
|
||||
}
|
||||
return new BigInteger(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this to the left by x, in place
|
||||
* @param {BigInteger} x - Shift value
|
||||
*/
|
||||
ileftShift(x: BigInteger) {
|
||||
this.value <<= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this to the left by x
|
||||
* @param {BigInteger} x - Shift value
|
||||
* @returns {BigInteger} this << x.
|
||||
*/
|
||||
leftShift(x: BigInteger) {
|
||||
return this.clone().ileftShift(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this to the right by x, in place
|
||||
* @param {BigInteger} x - Shift value
|
||||
*/
|
||||
irightShift(x: BigInteger) {
|
||||
this.value >>= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this to the right by x
|
||||
* @param {BigInteger} x - Shift value
|
||||
* @returns {BigInteger} this >> x.
|
||||
*/
|
||||
rightShift(x: BigInteger) {
|
||||
return this.clone().irightShift(x);
|
||||
}
|
||||
|
||||
ixor(x: BigInteger) {
|
||||
this.value ^= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
xor(x: BigInteger) {
|
||||
return this.clone().ixor(x);
|
||||
}
|
||||
|
||||
ibitwiseAnd(x: BigInteger) {
|
||||
this.value &= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
bitwiseAnd(x: BigInteger) {
|
||||
return this.clone().ibitwiseAnd(x);
|
||||
}
|
||||
|
||||
ibitwiseOr(x: BigInteger) {
|
||||
this.value |= x.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this value is equal to x
|
||||
* @param {BigInteger} x
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
equal(x: BigInteger) {
|
||||
return this.value === x.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this value is less than x
|
||||
* @param {BigInteger} x
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
lt(x: BigInteger) {
|
||||
return this.value < x.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this value is less than or equal to x
|
||||
* @param {BigInteger} x
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
lte(x: BigInteger) {
|
||||
return this.value <= x.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this value is greater than x
|
||||
* @param {BigInteger} x
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
gt(x: BigInteger) {
|
||||
return this.value > x.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this value is greater than or equal to x
|
||||
* @param {BigInteger} x
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
gte(x: BigInteger) {
|
||||
return this.value >= x.value;
|
||||
}
|
||||
|
||||
isZero() {
|
||||
return this.value === BigInt(0);
|
||||
}
|
||||
|
||||
isOne() {
|
||||
return this.value === BigInt(1);
|
||||
}
|
||||
|
||||
isNegative() {
|
||||
return this.value < BigInt(0);
|
||||
}
|
||||
|
||||
isEven() {
|
||||
return !(this.value & BigInt(1));
|
||||
}
|
||||
|
||||
abs() {
|
||||
const res = this.clone();
|
||||
if (this.isNegative()) {
|
||||
res.value = -res.value;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
negate() {
|
||||
const res = this.clone();
|
||||
res.value = -res.value;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this value as a string
|
||||
* @returns {String} this value.
|
||||
*/
|
||||
toString() {
|
||||
return this.value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this value as an exact Number (max 53 bits)
|
||||
* Fails if this value is too large
|
||||
* @returns {Number}
|
||||
*/
|
||||
toNumber() {
|
||||
const number = Number(this.value);
|
||||
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 {Number} i - Bit index
|
||||
* @returns {Number} Bit value.
|
||||
*/
|
||||
getBit(i: number) {
|
||||
const bit = (this.value >> BigInt(i)) & BigInt(1);
|
||||
return bit === BigInt(0) ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute bit length
|
||||
* @returns {Number} Bit length.
|
||||
*/
|
||||
bitLength() {
|
||||
const zero = new BigInteger(0);
|
||||
const one = new BigInteger(1);
|
||||
const negOne = new BigInteger(-1);
|
||||
|
||||
// -1n >> -1n is -1n
|
||||
// 1n >> 1n is 0n
|
||||
const target = this.isNegative() ? negOne : zero;
|
||||
let bitlen = 1;
|
||||
const tmp = this.clone();
|
||||
while (!tmp.irightShift(one).equal(target)) {
|
||||
bitlen++;
|
||||
}
|
||||
return bitlen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute byte length
|
||||
* @returns {Number} Byte length.
|
||||
*/
|
||||
byteLength() {
|
||||
const zero = new BigInteger(0);
|
||||
const negOne = new BigInteger(-1);
|
||||
|
||||
const target = this.isNegative() ? negOne : zero;
|
||||
const eight = new BigInteger(8);
|
||||
let len = 1;
|
||||
const tmp = this.clone();
|
||||
while (!tmp.irightShift(eight).equal(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}
|
||||
*/
|
||||
toUint8Array(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 = this.value.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;
|
||||
}
|
||||
}
|
216
src/crypto/biginteger.ts
Normal file
216
src/crypto/biginteger.ts
Normal file
@ -0,0 +1,216 @@
|
||||
// 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;
|
||||
}
|
@ -132,7 +132,7 @@ export function emeDecode(encoded, randomPayload) {
|
||||
* @param {Integer} emLen - Intended length in octets of the encoded message
|
||||
* @returns {Uint8Array} Encoded message.
|
||||
*/
|
||||
export async function emsaEncode(algo, hashed, emLen) {
|
||||
export function emsaEncode(algo, hashed, emLen) {
|
||||
let i;
|
||||
if (hashed.length !== hash.getHashByteLength(algo)) {
|
||||
throw new Error('Invalid hash length');
|
||||
|
@ -22,7 +22,7 @@
|
||||
import { getRandomBigInteger } from '../random';
|
||||
import util from '../../util';
|
||||
import { isProbablePrime } from './prime';
|
||||
import BigInteger from '../../biginteger';
|
||||
import { bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
|
||||
|
||||
/*
|
||||
TODO regarding the hash function, read:
|
||||
@ -30,6 +30,9 @@ import BigInteger from '../../biginteger';
|
||||
https://tools.ietf.org/html/rfc4880#section-14
|
||||
*/
|
||||
|
||||
const _0n = BigInt(0);
|
||||
const _1n = BigInt(1);
|
||||
|
||||
/**
|
||||
* DSA Sign function
|
||||
* @param {Integer} hashAlgo
|
||||
@ -42,24 +45,24 @@ import BigInteger from '../../biginteger';
|
||||
* @async
|
||||
*/
|
||||
export async function sign(hashAlgo, hashed, g, p, q, x) {
|
||||
const one = new BigInteger(1);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
x = new BigInteger(x);
|
||||
const _0n = BigInt(0);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
q = uint8ArrayToBigInt(q);
|
||||
g = uint8ArrayToBigInt(g);
|
||||
x = uint8ArrayToBigInt(x);
|
||||
|
||||
let k;
|
||||
let r;
|
||||
let s;
|
||||
let t;
|
||||
g = g.mod(p);
|
||||
x = x.mod(q);
|
||||
g = mod(g, p);
|
||||
x = mod(x, q);
|
||||
// If the output size of the chosen hash is larger than the number of
|
||||
// bits of q, the hash result is truncated to fit by taking the number
|
||||
// of leftmost bits equal to the number of bits of q. This (possibly
|
||||
// truncated) hash function result is treated as a number and used
|
||||
// directly in the DSA signature algorithm.
|
||||
const h = new BigInteger(hashed.subarray(0, q.byteLength())).mod(q);
|
||||
const h = mod(uint8ArrayToBigInt(hashed.subarray(0, byteLength(q))), q);
|
||||
// FIPS-186-4, section 4.6:
|
||||
// The values of r and s shall be checked to determine if r = 0 or s = 0.
|
||||
// If either r = 0 or s = 0, a new value of k shall be generated, and the
|
||||
@ -67,22 +70,22 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
|
||||
// or s = 0 if signatures are generated properly.
|
||||
while (true) {
|
||||
// See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
|
||||
k = await getRandomBigInteger(one, q); // returns in [1, q-1]
|
||||
r = g.modExp(k, p).imod(q); // (g**k mod p) mod q
|
||||
if (r.isZero()) {
|
||||
k = getRandomBigInteger(_1n, q); // returns in [1, q-1]
|
||||
r = mod(modExp(g, k, p), q); // (g**k mod p) mod q
|
||||
if (r === _0n) {
|
||||
continue;
|
||||
}
|
||||
const xr = x.mul(r).imod(q);
|
||||
t = h.add(xr).imod(q); // H(m) + x*r mod q
|
||||
s = k.modInv(q).imul(t).imod(q); // k**-1 * (H(m) + x*r) mod q
|
||||
if (s.isZero()) {
|
||||
const xr = mod(x * r, q);
|
||||
t = mod(h + xr, q); // H(m) + x*r mod q
|
||||
s = mod(modInv(k, q) * t, q); // k**-1 * (H(m) + x*r) mod q
|
||||
if (s === _0n) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return {
|
||||
r: r.toUint8Array('be', q.byteLength()),
|
||||
s: s.toUint8Array('be', q.byteLength())
|
||||
r: bigIntToUint8Array(r, 'be', byteLength(p)),
|
||||
s: bigIntToUint8Array(s, 'be', byteLength(p))
|
||||
};
|
||||
}
|
||||
|
||||
@ -100,35 +103,34 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
|
||||
* @async
|
||||
*/
|
||||
export async function verify(hashAlgo, r, s, hashed, g, p, q, y) {
|
||||
const zero = new BigInteger(0);
|
||||
r = new BigInteger(r);
|
||||
s = new BigInteger(s);
|
||||
r = uint8ArrayToBigInt(r);
|
||||
s = uint8ArrayToBigInt(s);
|
||||
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
q = uint8ArrayToBigInt(q);
|
||||
g = uint8ArrayToBigInt(g);
|
||||
y = uint8ArrayToBigInt(y);
|
||||
|
||||
if (r.lte(zero) || r.gte(q) ||
|
||||
s.lte(zero) || s.gte(q)) {
|
||||
if (r <= _0n || r >= q ||
|
||||
s <= _0n || s >= q) {
|
||||
util.printDebug('invalid DSA Signature');
|
||||
return false;
|
||||
}
|
||||
const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q);
|
||||
const w = s.modInv(q); // s**-1 mod q
|
||||
if (w.isZero()) {
|
||||
const h = mod(uint8ArrayToBigInt(hashed.subarray(0, byteLength(q))), q);
|
||||
const w = modInv(s, q); // s**-1 mod q
|
||||
if (w === _0n) {
|
||||
util.printDebug('invalid DSA Signature');
|
||||
return false;
|
||||
}
|
||||
|
||||
g = g.mod(p);
|
||||
y = y.mod(p);
|
||||
const u1 = h.mul(w).imod(q); // H(m) * w mod q
|
||||
const u2 = r.mul(w).imod(q); // r * w mod q
|
||||
const t1 = g.modExp(u1, p); // g**u1 mod p
|
||||
const t2 = y.modExp(u2, p); // y**u2 mod p
|
||||
const v = t1.mul(t2).imod(p).imod(q); // (g**u1 * y**u2 mod p) mod q
|
||||
return v.equal(r);
|
||||
g = mod(g, p);
|
||||
y = mod(y, p);
|
||||
const u1 = mod(h * w, q); // H(m) * w mod q
|
||||
const u2 = mod(r * w, q); // r * w mod q
|
||||
const t1 = modExp(g, u1, p); // g**u1 mod p
|
||||
const t2 = modExp(y, u2, p); // y**u2 mod p
|
||||
const v = mod(mod(t1 * t2, p), q); // (g**u1 * y**u2 mod p) mod q
|
||||
return v === r;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,20 +144,19 @@ export async function verify(hashAlgo, r, s, hashed, g, p, q, y) {
|
||||
* @async
|
||||
*/
|
||||
export async function validateParams(p, q, g, y, x) {
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
const one = new BigInteger(1);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
q = uint8ArrayToBigInt(q);
|
||||
g = uint8ArrayToBigInt(g);
|
||||
y = uint8ArrayToBigInt(y);
|
||||
// Check that 1 < g < p
|
||||
if (g.lte(one) || g.gte(p)) {
|
||||
if (g <= _1n || g >= p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that subgroup order q divides p-1
|
||||
*/
|
||||
if (!p.dec().mod(q).isZero()) {
|
||||
if (mod(p - _1n, q) !== _0n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -163,16 +164,16 @@ export async function validateParams(p, q, g, y, x) {
|
||||
* g has order q
|
||||
* Check that g ** q = 1 mod p
|
||||
*/
|
||||
if (!g.modExp(q, p).isOne()) {
|
||||
if (modExp(g, q, p) !== _1n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check q is large and probably prime (we mainly want to avoid small factors)
|
||||
*/
|
||||
const qSize = new BigInteger(q.bitLength());
|
||||
const n150 = new BigInteger(150);
|
||||
if (qSize.lt(n150) || !(await isProbablePrime(q, null, 32))) {
|
||||
const qSize = BigInt(bitLength(q));
|
||||
const _150n = BigInt(150);
|
||||
if (qSize < _150n || !isProbablePrime(q, null, 32)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -182,11 +183,11 @@ export async function validateParams(p, q, g, y, x) {
|
||||
*
|
||||
* Blinded exponentiation computes g**{rq + x} to compare to y
|
||||
*/
|
||||
x = new BigInteger(x);
|
||||
const two = new BigInteger(2);
|
||||
const r = await getRandomBigInteger(two.leftShift(qSize.dec()), two.leftShift(qSize)); // draw r of same size as q
|
||||
const rqx = q.mul(r).add(x);
|
||||
if (!y.equal(g.modExp(rqx, p))) {
|
||||
x = uint8ArrayToBigInt(x);
|
||||
const _2n = BigInt(2);
|
||||
const r = getRandomBigInteger(_2n << (qSize - _1n), _2n << qSize); // draw r of same size as q
|
||||
const rqx = q * r + x;
|
||||
if (y !== modExp(g, rqx, p)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,9 @@
|
||||
*/
|
||||
import { getRandomBigInteger } from '../random';
|
||||
import { emeEncode, emeDecode } from '../pkcs1';
|
||||
import BigInteger from '../../biginteger';
|
||||
import { bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
|
||||
|
||||
const _1n = BigInt(1);
|
||||
|
||||
/**
|
||||
* ElGamal Encryption function
|
||||
@ -34,19 +36,19 @@ import BigInteger from '../../biginteger';
|
||||
* @async
|
||||
*/
|
||||
export async function encrypt(data, p, g, y) {
|
||||
p = new BigInteger(p);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
g = uint8ArrayToBigInt(g);
|
||||
y = uint8ArrayToBigInt(y);
|
||||
|
||||
const padded = emeEncode(data, p.byteLength());
|
||||
const m = new BigInteger(padded);
|
||||
const padded = emeEncode(data, byteLength(p));
|
||||
const m = uint8ArrayToBigInt(padded);
|
||||
|
||||
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*
|
||||
// hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2]
|
||||
const k = await getRandomBigInteger(new BigInteger(1), p.dec());
|
||||
const k = getRandomBigInteger(_1n, p - _1n);
|
||||
return {
|
||||
c1: g.modExp(k, p).toUint8Array(),
|
||||
c2: y.modExp(k, p).imul(m).imod(p).toUint8Array()
|
||||
c1: bigIntToUint8Array(modExp(g, k, p)),
|
||||
c2: bigIntToUint8Array(mod(modExp(y, k, p) * m, p))
|
||||
};
|
||||
}
|
||||
|
||||
@ -63,13 +65,13 @@ export async function encrypt(data, p, g, y) {
|
||||
* @async
|
||||
*/
|
||||
export async function decrypt(c1, c2, p, x, randomPayload) {
|
||||
c1 = new BigInteger(c1);
|
||||
c2 = new BigInteger(c2);
|
||||
p = new BigInteger(p);
|
||||
x = new BigInteger(x);
|
||||
c1 = uint8ArrayToBigInt(c1);
|
||||
c2 = uint8ArrayToBigInt(c2);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
x = uint8ArrayToBigInt(x);
|
||||
|
||||
const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p);
|
||||
return emeDecode(padded.toUint8Array('be', p.byteLength()), randomPayload);
|
||||
const padded = mod(modInv(modExp(c1, x, p), p) * c2, p);
|
||||
return emeDecode(bigIntToUint8Array(padded, 'be', byteLength(p)), randomPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,20 +84,19 @@ export async function decrypt(c1, c2, p, x, randomPayload) {
|
||||
* @async
|
||||
*/
|
||||
export async function validateParams(p, g, y, x) {
|
||||
p = new BigInteger(p);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
g = uint8ArrayToBigInt(g);
|
||||
y = uint8ArrayToBigInt(y);
|
||||
|
||||
const one = new BigInteger(1);
|
||||
// Check that 1 < g < p
|
||||
if (g.lte(one) || g.gte(p)) {
|
||||
if (g <= _1n || g >= p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expect p-1 to be large
|
||||
const pSize = new BigInteger(p.bitLength());
|
||||
const n1023 = new BigInteger(1023);
|
||||
if (pSize.lt(n1023)) {
|
||||
const pSize = BigInt(bitLength(p));
|
||||
const _1023n = BigInt(1023);
|
||||
if (pSize < _1023n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -103,7 +104,7 @@ export async function validateParams(p, g, y, x) {
|
||||
* g should have order p-1
|
||||
* Check that g ** (p-1) = 1 mod p
|
||||
*/
|
||||
if (!g.modExp(p.dec(), p).isOne()) {
|
||||
if (modExp(g, p - _1n, p) !== _1n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -114,14 +115,15 @@ export async function validateParams(p, g, y, x) {
|
||||
* We just check g**i != 1 for all i up to a threshold
|
||||
*/
|
||||
let res = g;
|
||||
const i = new BigInteger(1);
|
||||
const threshold = new BigInteger(2).leftShift(new BigInteger(17)); // we want order > threshold
|
||||
while (i.lt(threshold)) {
|
||||
res = res.mul(g).imod(p);
|
||||
if (res.isOne()) {
|
||||
let i = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const threshold = _2n << BigInt(17); // we want order > threshold
|
||||
while (i < threshold) {
|
||||
res = mod(res * g, p);
|
||||
if (res === _1n) {
|
||||
return false;
|
||||
}
|
||||
i.iinc();
|
||||
i++;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,11 +132,10 @@ export async function validateParams(p, g, y, x) {
|
||||
*
|
||||
* Blinded exponentiation computes g**{r(p-1) + x} to compare to y
|
||||
*/
|
||||
x = new BigInteger(x);
|
||||
const two = new BigInteger(2);
|
||||
const r = await getRandomBigInteger(two.leftShift(pSize.dec()), two.leftShift(pSize)); // draw r of same size as p-1
|
||||
const rqx = p.dec().imul(r).iadd(x);
|
||||
if (!y.equal(g.modExp(rqx, p))) {
|
||||
x = uint8ArrayToBigInt(x);
|
||||
const r = getRandomBigInteger(_2n << (pSize - _1n), _2n << pSize); // draw r of same size as p-1
|
||||
const rqx = (p - _1n) * r + x;
|
||||
if (y !== modExp(g, rqx, p)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ import util from '../../../util';
|
||||
import { getRandomBytes } from '../../random';
|
||||
import hash from '../../hash';
|
||||
import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams, nodeCurves } from './oid_curves';
|
||||
import BigInteger from '../../../biginteger';
|
||||
import { bigIntToUint8Array } from '../../biginteger';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
@ -73,8 +73,8 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
|
||||
// lowS: non-canonical sig: https://stackoverflow.com/questions/74338846/ecdsa-signature-verification-mismatch
|
||||
const signature = nobleCurve.sign(hashed, privateKey, { lowS: false });
|
||||
return {
|
||||
r: new BigInteger(signature.r).toUint8Array('be', curve.payloadSize),
|
||||
s: new BigInteger(signature.s).toUint8Array('be', curve.payloadSize)
|
||||
r: bigIntToUint8Array(signature.r, 'be', curve.payloadSize),
|
||||
s: bigIntToUint8Array(signature.s, 'be', curve.payloadSize)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -19,21 +19,20 @@
|
||||
* @fileoverview Algorithms for probabilistic random prime generation
|
||||
* @module crypto/public_key/prime
|
||||
*/
|
||||
import BigInteger from '../../biginteger';
|
||||
import { bigIntToNumber, bitLength, gcd, getBit, mod, modExp } from '../biginteger';
|
||||
import { getRandomBigInteger } from '../random';
|
||||
|
||||
const _1n = BigInt(1);
|
||||
|
||||
/**
|
||||
* Generate a probably prime random number
|
||||
* @param {Integer} bits - Bit length of the prime
|
||||
* @param {BigInteger} e - Optional RSA exponent to check against the prime
|
||||
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
|
||||
* @returns BigInteger
|
||||
* @async
|
||||
* @param bits - Bit length of the prime
|
||||
* @param e - Optional RSA exponent to check against the prime
|
||||
* @param k - Optional number of iterations of Miller-Rabin test
|
||||
*/
|
||||
export async function randomProbablePrime(bits, e, k) {
|
||||
const one = new BigInteger(1);
|
||||
const min = one.leftShift(new BigInteger(bits - 1));
|
||||
const thirty = new BigInteger(30);
|
||||
export function randomProbablePrime(bits: number, e: bigint, k: number) {
|
||||
const _30n = BigInt(30);
|
||||
const min = _1n << BigInt(bits - 1);
|
||||
/*
|
||||
* We can avoid any multiples of 3 and 5 by looking at n mod 30
|
||||
* n mod 30 = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
||||
@ -42,40 +41,38 @@ export async function randomProbablePrime(bits, e, k) {
|
||||
*/
|
||||
const adds = [1, 6, 5, 4, 3, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 2];
|
||||
|
||||
const n = await getRandomBigInteger(min, min.leftShift(one));
|
||||
let i = n.mod(thirty).toNumber();
|
||||
let n = getRandomBigInteger(min, min << _1n);
|
||||
let i = bigIntToNumber(mod(n, _30n));
|
||||
|
||||
do {
|
||||
n.iadd(new BigInteger(adds[i]));
|
||||
n += BigInt(adds[i]);
|
||||
i = (i + adds[i]) % adds.length;
|
||||
// If reached the maximum, go back to the minimum.
|
||||
if (n.bitLength() > bits) {
|
||||
n.imod(min.leftShift(one)).iadd(min);
|
||||
i = n.mod(thirty).toNumber();
|
||||
if (bitLength(n) > bits) {
|
||||
n = mod(n, min << _1n); n += min;
|
||||
i = bigIntToNumber(mod(n, _30n));
|
||||
}
|
||||
} while (!await isProbablePrime(n, e, k));
|
||||
} while (!isProbablePrime(n, e, k));
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Probabilistic primality testing
|
||||
* @param {BigInteger} n - Number to test
|
||||
* @param {BigInteger} e - Optional RSA exponent to check against the prime
|
||||
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
|
||||
* @returns {boolean}
|
||||
* @async
|
||||
* @param n - Number to test
|
||||
* @param e - Optional RSA exponent to check against the prime
|
||||
* @param k - Optional number of iterations of Miller-Rabin test
|
||||
*/
|
||||
export async function isProbablePrime(n, e, k) {
|
||||
if (e && !n.dec().gcd(e).isOne()) {
|
||||
export function isProbablePrime(n: bigint, e: bigint, k: number) {
|
||||
if (e && gcd(n - _1n, e) !== _1n) {
|
||||
return false;
|
||||
}
|
||||
if (!await divisionTest(n)) {
|
||||
if (!divisionTest(n)) {
|
||||
return false;
|
||||
}
|
||||
if (!await fermat(n)) {
|
||||
if (!fermat(n)) {
|
||||
return false;
|
||||
}
|
||||
if (!await millerRabin(n, k)) {
|
||||
if (!millerRabin(n, k)) {
|
||||
return false;
|
||||
}
|
||||
// TODO implement the Lucas test
|
||||
@ -86,19 +83,16 @@ export async function isProbablePrime(n, e, k) {
|
||||
/**
|
||||
* Tests whether n is probably prime or not using Fermat's test with b = 2.
|
||||
* Fails if b^(n-1) mod n != 1.
|
||||
* @param {BigInteger} n - Number to test
|
||||
* @param {BigInteger} b - Optional Fermat test base
|
||||
* @returns {boolean}
|
||||
* @param n - Number to test
|
||||
* @param b - Optional Fermat test base
|
||||
*/
|
||||
export async function fermat(n, b) {
|
||||
b = b || new BigInteger(2);
|
||||
return b.modExp(n.dec(), n).isOne();
|
||||
export function fermat(n: bigint, b = BigInt(2)) {
|
||||
return modExp(b, n - _1n, n) === _1n;
|
||||
}
|
||||
|
||||
export async function divisionTest(n) {
|
||||
return smallPrimes.every(m => {
|
||||
return n.mod(new BigInteger(m)) !== 0;
|
||||
});
|
||||
export function divisionTest(n: bigint) {
|
||||
const _0n = BigInt(0);
|
||||
return smallPrimes.every(m => mod(n, m) !== _0n);
|
||||
}
|
||||
|
||||
// https://github.com/gpg/libgcrypt/blob/master/cipher/primegen.c
|
||||
@ -182,7 +176,7 @@ const smallPrimes = [
|
||||
4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889,
|
||||
4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951,
|
||||
4957, 4967, 4969, 4973, 4987, 4993, 4999
|
||||
];
|
||||
].map(n => BigInt(n));
|
||||
|
||||
|
||||
// Miller-Rabin - Miller Rabin algorithm for primality test
|
||||
@ -217,42 +211,42 @@ const smallPrimes = [
|
||||
/**
|
||||
* Tests whether n is probably prime or not using the Miller-Rabin test.
|
||||
* See HAC Remark 4.28.
|
||||
* @param {BigInteger} n - Number to test
|
||||
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
|
||||
* @param {Function} rand - Optional function to generate potential witnesses
|
||||
* @param n - Number to test
|
||||
* @param k - Optional number of iterations of Miller-Rabin test
|
||||
* @param rand - Optional function to generate potential witnesses
|
||||
* @returns {boolean}
|
||||
* @async
|
||||
*/
|
||||
export async function millerRabin(n, k, rand) {
|
||||
const len = n.bitLength();
|
||||
export function millerRabin(n: bigint, k: number, rand?: () => bigint) {
|
||||
const len = bitLength(n);
|
||||
|
||||
if (!k) {
|
||||
k = Math.max(1, (len / 48) | 0);
|
||||
}
|
||||
|
||||
const n1 = n.dec(); // n - 1
|
||||
const n1 = n - _1n; // n - 1
|
||||
|
||||
// Find d and s, (n - 1) = (2 ^ s) * d;
|
||||
let s = 0;
|
||||
while (!n1.getBit(s)) { s++; }
|
||||
const d = n.rightShift(new BigInteger(s));
|
||||
while (!getBit(n1, s)) { s++; }
|
||||
const d = n >> BigInt(s);
|
||||
|
||||
for (; k > 0; k--) {
|
||||
const a = rand ? rand() : await getRandomBigInteger(new BigInteger(2), n1);
|
||||
const a = rand ? rand() : getRandomBigInteger(BigInt(2), n1);
|
||||
|
||||
let x = a.modExp(d, n);
|
||||
if (x.isOne() || x.equal(n1)) {
|
||||
let x = modExp(a, d, n);
|
||||
if (x === _1n || x === n1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = 1; i < s; i++) {
|
||||
x = x.mul(x).mod(n);
|
||||
x = mod(x * x, n);
|
||||
|
||||
if (x.isOne()) {
|
||||
if (x === _1n) {
|
||||
return false;
|
||||
}
|
||||
if (x.equal(n1)) {
|
||||
if (x === n1) {
|
||||
break;
|
||||
}
|
||||
}
|
@ -25,10 +25,11 @@ import util from '../../util';
|
||||
import { uint8ArrayToB64, b64ToUint8Array } from '../../encoding/base64';
|
||||
import { emsaEncode, emeEncode, emeDecode } from '../pkcs1';
|
||||
import enums from '../../enums';
|
||||
import BigInteger from '../../biginteger';
|
||||
import { bigIntToNumber, bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
const _1n = BigInt(1);
|
||||
|
||||
/** Create signature
|
||||
* @param {module:enums.hash} hashAlgo - Hash algorithm
|
||||
@ -142,14 +143,14 @@ export async function decrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||
* @async
|
||||
*/
|
||||
export async function generate(bits, e) {
|
||||
e = new BigInteger(e);
|
||||
e = BigInt(e);
|
||||
|
||||
// Native RSA keygen using Web Crypto
|
||||
if (util.getWebCrypto()) {
|
||||
const keyGenOpt = {
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: bits, // the specified keysize in bits
|
||||
publicExponent: e.toUint8Array(), // take three bytes (max 65537) for exponent
|
||||
publicExponent: bigIntToUint8Array(e), // take three bytes (max 65537) for exponent
|
||||
hash: {
|
||||
name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify'
|
||||
}
|
||||
@ -164,7 +165,7 @@ export async function generate(bits, e) {
|
||||
} else if (util.getNodeCrypto()) {
|
||||
const opts = {
|
||||
modulusLength: bits,
|
||||
publicExponent: e.toNumber(),
|
||||
publicExponent: bigIntToNumber(e),
|
||||
publicKeyEncoding: { type: 'pkcs1', format: 'jwk' },
|
||||
privateKeyEncoding: { type: 'pkcs1', format: 'jwk' }
|
||||
};
|
||||
@ -187,26 +188,26 @@ export async function generate(bits, e) {
|
||||
let q;
|
||||
let n;
|
||||
do {
|
||||
q = await randomProbablePrime(bits - (bits >> 1), e, 40);
|
||||
p = await randomProbablePrime(bits >> 1, e, 40);
|
||||
n = p.mul(q);
|
||||
} while (n.bitLength() !== bits);
|
||||
q = randomProbablePrime(bits - (bits >> 1), e, 40);
|
||||
p = randomProbablePrime(bits >> 1, e, 40);
|
||||
n = p * q;
|
||||
} while (bitLength(n) !== bits);
|
||||
|
||||
const phi = p.dec().imul(q.dec());
|
||||
const phi = (p - _1n) * (q - _1n);
|
||||
|
||||
if (q.lt(p)) {
|
||||
if (q < p) {
|
||||
[p, q] = [q, p];
|
||||
}
|
||||
|
||||
return {
|
||||
n: n.toUint8Array(),
|
||||
e: e.toUint8Array(),
|
||||
d: e.modInv(phi).toUint8Array(),
|
||||
p: p.toUint8Array(),
|
||||
q: q.toUint8Array(),
|
||||
n: bigIntToUint8Array(n),
|
||||
e: bigIntToUint8Array(e),
|
||||
d: bigIntToUint8Array(modInv(e, phi)),
|
||||
p: bigIntToUint8Array(p),
|
||||
q: bigIntToUint8Array(q),
|
||||
// dp: d.mod(p.subn(1)),
|
||||
// dq: d.mod(q.subn(1)),
|
||||
u: p.modInv(q).toUint8Array()
|
||||
u: bigIntToUint8Array(modInv(p, q))
|
||||
};
|
||||
}
|
||||
|
||||
@ -222,24 +223,24 @@ export async function generate(bits, e) {
|
||||
* @async
|
||||
*/
|
||||
export async function validateParams(n, e, d, p, q, u) {
|
||||
n = new BigInteger(n);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
n = uint8ArrayToBigInt(n);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
q = uint8ArrayToBigInt(q);
|
||||
|
||||
// expect pq = n
|
||||
if (!p.mul(q).equal(n)) {
|
||||
if ((p * q) !== n) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const two = new BigInteger(2);
|
||||
const _2n = BigInt(2);
|
||||
// expect p*u = 1 mod q
|
||||
u = new BigInteger(u);
|
||||
if (!p.mul(u).mod(q).isOne()) {
|
||||
u = uint8ArrayToBigInt(u);
|
||||
if (mod(p * u, q) !== BigInt(1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
e = new BigInteger(e);
|
||||
d = new BigInteger(d);
|
||||
e = uint8ArrayToBigInt(e);
|
||||
d = uint8ArrayToBigInt(d);
|
||||
/**
|
||||
* In RSA pkcs#1 the exponents (d, e) are inverses modulo lcm(p-1, q-1)
|
||||
* We check that [de = 1 mod (p-1)] and [de = 1 mod (q-1)]
|
||||
@ -247,11 +248,11 @@ export async function validateParams(n, e, d, p, q, u) {
|
||||
*
|
||||
* We blind the multiplication with r, and check that rde = r mod lcm(p-1, q-1)
|
||||
*/
|
||||
const nSizeOver3 = new BigInteger(Math.floor(n.bitLength() / 3));
|
||||
const r = await getRandomBigInteger(two, two.leftShift(nSizeOver3)); // r in [ 2, 2^{|n|/3} ) < p and q
|
||||
const rde = r.mul(d).mul(e);
|
||||
const nSizeOver3 = BigInt(Math.floor(bitLength(n) / 3));
|
||||
const r = getRandomBigInteger(_2n, _2n << nSizeOver3); // r in [ 2, 2^{|n|/3} ) < p and q
|
||||
const rde = r * d * e;
|
||||
|
||||
const areInverses = rde.mod(p.dec()).equal(r) && rde.mod(q.dec()).equal(r);
|
||||
const areInverses = mod(rde, p - _1n) === r && mod(rde, q - _1n) === r;
|
||||
if (!areInverses) {
|
||||
return false;
|
||||
}
|
||||
@ -260,13 +261,13 @@ export async function validateParams(n, e, d, p, q, u) {
|
||||
}
|
||||
|
||||
async function bnSign(hashAlgo, n, d, hashed) {
|
||||
n = new BigInteger(n);
|
||||
const m = new BigInteger(await emsaEncode(hashAlgo, hashed, n.byteLength()));
|
||||
d = new BigInteger(d);
|
||||
if (m.gte(n)) {
|
||||
n = uint8ArrayToBigInt(n);
|
||||
const m = uint8ArrayToBigInt(emsaEncode(hashAlgo, hashed, byteLength(n)));
|
||||
d = uint8ArrayToBigInt(d);
|
||||
if (m >= n) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
}
|
||||
return m.modExp(d, n).toUint8Array('be', n.byteLength());
|
||||
return bigIntToUint8Array(modExp(m, d, n), 'be', byteLength(n));
|
||||
}
|
||||
|
||||
async function webSign(hashName, data, n, e, d, p, q, u) {
|
||||
@ -296,14 +297,14 @@ async function nodeSign(hashAlgo, data, n, e, d, p, q, u) {
|
||||
}
|
||||
|
||||
async function bnVerify(hashAlgo, s, n, e, hashed) {
|
||||
n = new BigInteger(n);
|
||||
s = new BigInteger(s);
|
||||
e = new BigInteger(e);
|
||||
if (s.gte(n)) {
|
||||
n = uint8ArrayToBigInt(n);
|
||||
s = uint8ArrayToBigInt(s);
|
||||
e = uint8ArrayToBigInt(e);
|
||||
if (s >= n) {
|
||||
throw new Error('Signature size cannot exceed modulus size');
|
||||
}
|
||||
const EM1 = s.modExp(e, n).toUint8Array('be', n.byteLength());
|
||||
const EM2 = await emsaEncode(hashAlgo, hashed, n.byteLength());
|
||||
const EM1 = bigIntToUint8Array(modExp(s, e, n), 'be', byteLength(n));
|
||||
const EM2 = emsaEncode(hashAlgo, hashed, byteLength(n));
|
||||
return util.equalsUint8Array(EM1, EM2);
|
||||
}
|
||||
|
||||
@ -339,13 +340,13 @@ async function nodeEncrypt(data, n, e) {
|
||||
}
|
||||
|
||||
async function bnEncrypt(data, n, e) {
|
||||
n = new BigInteger(n);
|
||||
data = new BigInteger(emeEncode(data, n.byteLength()));
|
||||
e = new BigInteger(e);
|
||||
if (data.gte(n)) {
|
||||
n = uint8ArrayToBigInt(n);
|
||||
data = uint8ArrayToBigInt(emeEncode(data, byteLength(n)));
|
||||
e = uint8ArrayToBigInt(e);
|
||||
if (data >= n) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
}
|
||||
return data.modExp(e, n).toUint8Array('be', n.byteLength());
|
||||
return bigIntToUint8Array(modExp(data, e, n), 'be', byteLength(n));
|
||||
}
|
||||
|
||||
async function nodeDecrypt(data, n, e, d, p, q, u) {
|
||||
@ -360,34 +361,32 @@ async function nodeDecrypt(data, n, e, d, p, q, u) {
|
||||
}
|
||||
|
||||
async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||
data = new BigInteger(data);
|
||||
n = new BigInteger(n);
|
||||
e = new BigInteger(e);
|
||||
d = new BigInteger(d);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
u = new BigInteger(u);
|
||||
if (data.gte(n)) {
|
||||
data = uint8ArrayToBigInt(data);
|
||||
n = uint8ArrayToBigInt(n);
|
||||
e = uint8ArrayToBigInt(e);
|
||||
d = uint8ArrayToBigInt(d);
|
||||
p = uint8ArrayToBigInt(p);
|
||||
q = uint8ArrayToBigInt(q);
|
||||
u = uint8ArrayToBigInt(u);
|
||||
if (data >= n) {
|
||||
throw new Error('Data too large.');
|
||||
}
|
||||
const dq = d.mod(q.dec()); // d mod (q-1)
|
||||
const dp = d.mod(p.dec()); // d mod (p-1)
|
||||
const dq = mod(d, q - _1n); // d mod (q-1)
|
||||
const dp = mod(d, p - _1n); // d mod (p-1)
|
||||
|
||||
const unblinder = (await getRandomBigInteger(new BigInteger(2), n)).mod(n);
|
||||
const blinder = unblinder.modInv(n).modExp(e, n);
|
||||
data = data.mul(blinder).mod(n);
|
||||
const unblinder = getRandomBigInteger(BigInt(2), n);
|
||||
const blinder = modExp(modInv(unblinder, n), e, n);
|
||||
data = mod(data * blinder, n);
|
||||
|
||||
const mp = modExp(data, dp, p); // data**{d mod (q-1)} mod p
|
||||
const mq = modExp(data, dq, q); // data**{d mod (p-1)} mod q
|
||||
const h = mod(u * (mq - mp), q); // u * (mq-mp) mod q (operands already < q)
|
||||
|
||||
const mp = data.modExp(dp, p); // data**{d mod (q-1)} mod p
|
||||
const mq = data.modExp(dq, q); // data**{d mod (p-1)} mod q
|
||||
const h = u.mul(mq.sub(mp)).mod(q); // u * (mq-mp) mod q (operands already < q)
|
||||
let result = h * p + mp; // result < n due to relations above
|
||||
|
||||
let result = h.mul(p).add(mp); // result < n due to relations above
|
||||
result = mod(result * unblinder, n);
|
||||
|
||||
result = result.mul(unblinder).mod(n);
|
||||
|
||||
|
||||
return emeDecode(result.toUint8Array('be', n.byteLength()), randomPayload);
|
||||
return emeDecode(bigIntToUint8Array(result, 'be', byteLength(n)), randomPayload);
|
||||
}
|
||||
|
||||
/** Convert Openpgp private key params to jwk key according to
|
||||
@ -401,14 +400,14 @@ async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||
* @param {Uint8Array} u
|
||||
*/
|
||||
async function privateToJWK(n, e, d, p, q, u) {
|
||||
const pNum = new BigInteger(p);
|
||||
const qNum = new BigInteger(q);
|
||||
const dNum = new BigInteger(d);
|
||||
const pNum = uint8ArrayToBigInt(p);
|
||||
const qNum = uint8ArrayToBigInt(q);
|
||||
const dNum = uint8ArrayToBigInt(d);
|
||||
|
||||
let dq = dNum.mod(qNum.dec()); // d mod (q-1)
|
||||
let dp = dNum.mod(pNum.dec()); // d mod (p-1)
|
||||
dp = dp.toUint8Array();
|
||||
dq = dq.toUint8Array();
|
||||
let dq = mod(dNum, qNum - _1n); // d mod (q-1)
|
||||
let dp = mod(dNum, pNum - _1n); // d mod (p-1)
|
||||
dp = bigIntToUint8Array(dp);
|
||||
dq = bigIntToUint8Array(dq);
|
||||
return {
|
||||
kty: 'RSA',
|
||||
n: uint8ArrayToB64(n, true),
|
||||
@ -444,7 +443,7 @@ function publicToJWK(n, e) {
|
||||
function jwkToPrivate(jwk, e) {
|
||||
return {
|
||||
n: b64ToUint8Array(jwk.n),
|
||||
e: e.toUint8Array(),
|
||||
e: bigIntToUint8Array(e),
|
||||
d: b64ToUint8Array(jwk.d),
|
||||
// switch p and q
|
||||
p: b64ToUint8Array(jwk.q),
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @fileoverview Provides tools for retrieving secure randomness from browsers or Node.js
|
||||
* @module crypto/random
|
||||
*/
|
||||
import BigInteger from '../biginteger';
|
||||
import { byteLength, mod, uint8ArrayToBigInt } from './biginteger';
|
||||
import util from '../util';
|
||||
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
@ -45,23 +45,23 @@ export function getRandomBytes(length) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a secure random BigInteger that is greater than or equal to min and less than max.
|
||||
* @param {module:BigInteger} min - Lower bound, included
|
||||
* @param {module:BigInteger} max - Upper bound, excluded
|
||||
* @returns {Promise<module:BigInteger>} Random BigInteger.
|
||||
* Create a secure random BigInt that is greater than or equal to min and less than max.
|
||||
* @param {bigint} min - Lower bound, included
|
||||
* @param {bigint} max - Upper bound, excluded
|
||||
* @returns {bigint} Random BigInt.
|
||||
* @async
|
||||
*/
|
||||
export async function getRandomBigInteger(min, max) {
|
||||
if (max.lt(min)) {
|
||||
export function getRandomBigInteger(min, max) {
|
||||
if (max < min) {
|
||||
throw new Error('Illegal parameter value: max <= min');
|
||||
}
|
||||
|
||||
const modulus = max.sub(min);
|
||||
const bytes = modulus.byteLength();
|
||||
const modulus = max - min;
|
||||
const bytes = byteLength(modulus);
|
||||
|
||||
// Using a while loop is necessary to avoid bias introduced by the mod operation.
|
||||
// However, we request 64 extra random bits so that the bias is negligible.
|
||||
// Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
|
||||
const r = new BigInteger(getRandomBytes(bytes + 8));
|
||||
return r.mod(modulus).add(min);
|
||||
const r = uint8ArrayToBigInt(getRandomBytes(bytes + 8));
|
||||
return mod(r, modulus) + min;
|
||||
}
|
||||
|
96
test/crypto/biginteger.js
Normal file
96
test/crypto/biginteger.js
Normal file
@ -0,0 +1,96 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { bigIntToUint8Array, bitLength, byteLength, gcd, getBit, modExp, modInv } from '../../src/crypto/biginteger';
|
||||
import { getRandomBytes } from '../../src/crypto/random';
|
||||
|
||||
async function getRandomBN(min, max) {
|
||||
if (max.cmp(min) <= 0) {
|
||||
throw new Error('Illegal parameter value: max <= min');
|
||||
}
|
||||
|
||||
const modulus = max.sub(min);
|
||||
const bytes = modulus.byteLength();
|
||||
const r = new BN(getRandomBytes(bytes + 8));
|
||||
return r.mod(modulus).add(min);
|
||||
}
|
||||
|
||||
|
||||
export default () => describe('BigInt', () => {
|
||||
it('bitLength is correct', function() {
|
||||
const n = BigInt(127);
|
||||
const expected = 7;
|
||||
expect(bitLength(n)).to.equal(expected);
|
||||
expect(bitLength(n + BigInt(1))).to.equal(expected + 1);
|
||||
});
|
||||
|
||||
it('byteLength is correct', function() {
|
||||
const n = BigInt(65535);
|
||||
const expected = 2;
|
||||
expect(byteLength(n)).to.equal(expected);
|
||||
expect(byteLength(n + BigInt(1))).to.equal(expected + 1);
|
||||
});
|
||||
|
||||
it('toUint8Array is correct', function() {
|
||||
const nString = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027';
|
||||
const n = BigInt(nString);
|
||||
const paddedSize = Number(byteLength(n)) + 1;
|
||||
// big endian, unpadded
|
||||
let expected = new BN(nString).toArrayLike(Uint8Array);
|
||||
expect(bigIntToUint8Array(n)).to.deep.equal(expected);
|
||||
// big endian, padded
|
||||
expected = new BN(nString).toArrayLike(Uint8Array, 'be', paddedSize);
|
||||
expect(bigIntToUint8Array(n, 'be', paddedSize)).to.deep.equal(expected);
|
||||
// little endian, unpadded
|
||||
expected = new BN(nString).toArrayLike(Uint8Array, 'le');
|
||||
expect(bigIntToUint8Array(n, 'le')).to.deep.equal(expected);
|
||||
//little endian, padded
|
||||
expected = new BN(nString).toArrayLike(Uint8Array, 'le', paddedSize);
|
||||
expect(bigIntToUint8Array(n, 'le', paddedSize)).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('modExp is correct (large values)', function() {
|
||||
const stringX = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027';
|
||||
const stringE = '21139356010872569239159922781526379521587348169074209285187910481667533072168468011617194695181255483288792585413365359733692097084373249198758148704369207793873998901870577262254971784191473102265830193058813215898765238784670469696574407580179153118937858890572095234316482449291777882525949871374961971753';
|
||||
const stringN = '129189808515414783602892982235788912674846062846614219472827821758734760420002631653235573915244294540972376140705505703576175711417114803419704967903726436285518767606681184247119430411311152556442947708732584954518890222684529678365388350886907287414896703685680210648760841628375425909680236584021041565183';
|
||||
const x = BigInt(stringX);
|
||||
const e = BigInt(stringE);
|
||||
const n = BigInt(stringN);
|
||||
|
||||
const got = modExp(x, e, n);
|
||||
const expected = new BN(stringX).toRed(BN.red(new BN(stringN))).redPow(new BN(stringE));
|
||||
// different formats, it's easier to compare strings
|
||||
expect(got.toString(), expected.toString());
|
||||
});
|
||||
|
||||
it('gcd is correct', async function() {
|
||||
const aBN = await getRandomBN(new BN(2), new BN(200));
|
||||
const bBN = await getRandomBN(new BN(2), new BN(200));
|
||||
if (aBN.isEven()) aBN.iaddn(1);
|
||||
const a = BigInt(aBN.toString());
|
||||
const b = BigInt(bBN.toString());
|
||||
const expected = aBN.gcd(bBN);
|
||||
expect(gcd(a, b).toString()).to.equal(expected.toString());
|
||||
});
|
||||
|
||||
it('modular inversion is correct', async function() {
|
||||
const moduloBN = new BN(229); // this is a prime
|
||||
const baseBN = await getRandomBN(new BN(2), moduloBN);
|
||||
const a = BigInt(baseBN.toString());
|
||||
const n = BigInt(moduloBN.toString());
|
||||
const expected = baseBN.invm(moduloBN);
|
||||
expect(modInv(a, n).toString()).to.equal(expected.toString());
|
||||
// test negative operand
|
||||
const expectedNegated = baseBN.neg().invm(moduloBN);
|
||||
expect(modInv(-a, n).toString()).to.equal(expectedNegated.toString());
|
||||
expect(() => modInv(a * n, n)).to.throw(/Inverse does not exist/);
|
||||
});
|
||||
|
||||
it('getBit is correct', async function() {
|
||||
const i = 5;
|
||||
const nBN = await getRandomBN(new BN(2), new BN(200));
|
||||
const n = BigInt(nBN.toString());
|
||||
const expected = nBN.testn(5) ? 1 : 0;
|
||||
expect(getBit(n, i)).to.equal(expected);
|
||||
});
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
import testBigInteger from './biginteger';
|
||||
import testCipher from './cipher';
|
||||
import testHash from './hash';
|
||||
import testCrypto from './crypto';
|
||||
@ -14,6 +15,7 @@ import testRSA from './rsa';
|
||||
import testValidate from './validate';
|
||||
|
||||
export default () => describe('Crypto', function () {
|
||||
testBigInteger();
|
||||
testCipher();
|
||||
testHash();
|
||||
testCrypto();
|
||||
|
@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/new
|
||||
chaiUse(chaiAsPromised);
|
||||
|
||||
import openpgp from '../initOpenpgp.js';
|
||||
import BigInteger from '../../src/biginteger.ts';
|
||||
import { bigIntToUint8Array, modExp, uint8ArrayToBigInt } from '../../src/crypto/biginteger.ts';
|
||||
|
||||
const armoredDSAKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
@ -390,11 +390,12 @@ export default () => {
|
||||
const keyPacket = await cloneKeyPacket(egKey);
|
||||
const { p, g } = keyPacket.publicParams;
|
||||
|
||||
const pBN = new BigInteger(p);
|
||||
const gBN = new BigInteger(g);
|
||||
const _1n = BigInt(1);
|
||||
const pBN = uint8ArrayToBigInt(p);
|
||||
const gBN = uint8ArrayToBigInt(g);
|
||||
// g**(p-1)/2 has order 2
|
||||
const gOrd2 = gBN.modExp(pBN.dec().irightShift(new BigInteger(1)), pBN);
|
||||
keyPacket.publicParams.g = gOrd2.toUint8Array();
|
||||
const gOrd2 = modExp(gBN, (pBN - _1n) >> _1n, pBN);
|
||||
keyPacket.publicParams.g = bigIntToUint8Array(gOrd2);
|
||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,6 @@
|
||||
"allowJs": true,
|
||||
"paths": {
|
||||
"openpgp": [ "." ]
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user