diff --git a/build/typings/globals.d.ts b/build/typings/globals.d.ts new file mode 100644 index 0000000..25a8a5c --- /dev/null +++ b/build/typings/globals.d.ts @@ -0,0 +1,3 @@ +declare const IS_BROWSER: boolean +declare const _MODULE_TYPE: string +declare const _NPM_PKG_VERSION: string diff --git a/src/ts/crt.ts b/src/ts/crt.ts new file mode 100644 index 0000000..354c6c5 --- /dev/null +++ b/src/ts/crt.ts @@ -0,0 +1,33 @@ +import { modInv } from './modInv.js' +import { toZn } from './toZn.js' + +/** + * Chinese remainder theorem states that if one knows the remainders of the Euclidean division of an integer n by several integers, then one can determine uniquely the remainder of the division of n by the product of these integers, under the condition that the divisors are pairwise coprime (no two divisors share a common factor other than 1). Provided that n_i are pairwise coprime, and a_i any integers, this function returns a solution for the following system of equations: + x ≡ a_1 mod n_1 + x ≡ a_2 mod n_2 + ⋮ + x ≡ a_k mod n_k + * + * @param remainders the array of remainders a_i. For example [17n, 243n, 344n] + * @param modulos the array of modulos n_i. For example [769n, 2017n, 47701n] + * @param modulo the product of all modulos. Provided here just to save some operations if it is already known + * @returns x + */ +export function crt ( + remainders: bigint[], + modulos: bigint[], + modulo?: bigint +): bigint { + if (remainders.length !== modulos.length) { + throw new RangeError('The remainders and modulos arrays should have the same length') + } + + const product = modulo ?? modulos.reduce((acc, val) => acc * val, 1n) + + return modulos.reduce((sum, mod, index) => { + const partialProduct = product / mod + const inverse = modInv(partialProduct, mod) + const toAdd = ((partialProduct * inverse) % product * remainders[index]) % product + return toZn(sum + toAdd, product) + }, 0n) +} diff --git a/src/ts/modAdd.ts b/src/ts/modAdd.ts new file mode 100644 index 0000000..ae7a8db --- /dev/null +++ b/src/ts/modAdd.ts @@ -0,0 +1,13 @@ +import { toZn } from './toZn.js' + +/** + * Modular addition of (a_1 + ... + a_r) mod n + * @param addends an array of the numbers a_i to add. For example [3, 12353251235n, 1243, -12341232545990n] + * @param n the modulo + * @returns The smallest positive integer that is congruent with (a_1 + ... + a_r) mod n + */ +export function modAdd (addends: Array, n: number | bigint): bigint { + const mod = BigInt(n) + const as = addends.map(a => BigInt(a) % mod) + return toZn(as.reduce((sum, a) => sum + a % mod, 0n), mod) +} diff --git a/src/ts/modMultiply.ts b/src/ts/modMultiply.ts new file mode 100644 index 0000000..be6df01 --- /dev/null +++ b/src/ts/modMultiply.ts @@ -0,0 +1,13 @@ +import { toZn } from './toZn.js' + +/** +* Modular addition of (a_1 * ... * a_r) mod n + * @param factors an array of the numbers a_i to multiply. For example [3, 12353251235n, 1243, -12341232545990n] + * @param n the modulo + * @returns The smallest positive integer that is congruent with (a_1 * ... * a_r) mod n + */ +export function modMultiply (factors: Array, n: number | bigint): bigint { + const mod = BigInt(n) + const as = factors.map(a => BigInt(a) % mod) + return toZn(as.reduce((prod, a) => prod * a % mod, 1n), mod) +} diff --git a/src/ts/phi.ts b/src/ts/phi.ts new file mode 100644 index 0000000..c09cdef --- /dev/null +++ b/src/ts/phi.ts @@ -0,0 +1,13 @@ +export type PrimeFactorization = Array<[bigint, bigint]> + +/** + * A function that computes the Euler's totien function of a number n, whose prime power factorization is known + * + * @param primeFactorization an array of arrays containing the prime power factorization, i.e. for n = (p1**k1)*(p2**k2)*...*(pr**kr), one should provide [[p1, k1], [p2, k2], ... , [pr, kr]] + * @returns phi((p1**k1)*(p2**k2)*...*(pr**kr)) + */ +export function phi (primeFactorization: PrimeFactorization): bigint { + return primeFactorization.map(v => (v[0] ** (v[1] - 1n)) * (v[0] - 1n)).reduce((prev, curr) => { + return curr * prev + }, 1n) +} diff --git a/test/crt.ts b/test/crt.ts new file mode 100644 index 0000000..648ea37 --- /dev/null +++ b/test/crt.ts @@ -0,0 +1,50 @@ +import * as bma from '#pkg' + +describe('crt', function () { + const tests = [ + { + input: { + remainders: [2n, 3n, 10n], + modulos: [5n, 7n, 11n] + }, + output: 87n + }, + { + input: { + remainders: [3n, 3n, 4n], + modulos: [7n, 5n, 12n], + modulo: 7n * 5n * 12n + }, + output: 388n + } + ] + const invalidTests = [ + { + input: { + remainders: [2n, 3n, 10n], + modulos: [5n, 7n] + }, + output: 87n + } + ] + for (const test of tests) { + describe(`crt([${test.input.remainders.toString()}], [${test.input.modulos.toString()}])`, function () { + it(`should return ${test.output}`, function () { + const ret = bma.crt(test.input.remainders, test.input.modulos, test.input.modulo) + chai.expect(ret).to.equal(test.output) + }) + }) + } + for (const test of invalidTests) { + describe(`crt([${test.input.remainders.toString()}], [${test.input.modulos.toString()}])`, function () { + it('should throw RangeError', function () { + try { + bma.crt(test.input.remainders, test.input.modulos) + throw new Error('should have failed') + } catch (err) { + chai.expect(err).to.be.instanceOf(RangeError) + } + }) + }) + } +}) diff --git a/test/modAdd.ts b/test/modAdd.ts new file mode 100644 index 0000000..dbbddbd --- /dev/null +++ b/test/modAdd.ts @@ -0,0 +1,31 @@ +import * as bma from '#pkg' + +describe('modAdd', function () { + const inputs = [ + { + addends: [1n, 14n, 5n], + n: 5n, + result: 0n + }, + { + addends: [98146598146508942650812465n, 971326598235697821592183520352089356n], + n: 972136523n, + result: 88640188n + }, + { + addends: [98146598146508942650812465n, -971326598235697821592183520352089356n], + n: 972136523n, + result: 133225889n + } + ] + + for (const input of inputs) { + describe(`modAdd([${input.addends.toString()}], ${input.n})`, function () { + it(`should return ${input.result}`, function () { + const ret = bma.modAdd(input.addends, input.n) + // chai.assert( String(ret) === String(input.modInv) ); + chai.expect(String(ret)).to.be.equal(String(input.result)) + }) + }) + } +}) diff --git a/test/modMultiply.ts b/test/modMultiply.ts new file mode 100644 index 0000000..31cc994 --- /dev/null +++ b/test/modMultiply.ts @@ -0,0 +1,30 @@ +import * as bma from '#pkg' + +describe('modMultiply', function () { + const inputs = [ + { + factors: [-1n, 1n, 19n], + n: 5n, + result: 1n + }, + { + factors: [98146598146508942650812465n, 971326598235697821592183520352089356n], + n: 972136523n, + result: 326488233n + }, + { + factors: [98146598146508942650812465n, -971326598235697821592183520352089356n], + n: 972136523n, + result: 645648290n + } + ] + + for (const input of inputs) { + describe(`modMultiply([${input.factors.toString()}], ${input.n})`, function () { + it(`should return ${input.result}`, function () { + const ret = bma.modMultiply(input.factors, input.n) + chai.expect(String(ret)).to.be.equal(String(input.result)) + }) + }) + } +}) diff --git a/test/phi.ts b/test/phi.ts new file mode 100644 index 0000000..c49dffd --- /dev/null +++ b/test/phi.ts @@ -0,0 +1,22 @@ +import * as bma from '#pkg' + +describe('phi', function () { + const tests = [ + { + input: [[17n, 1n], [19n, 1n]] as bma.PrimeFactorization, + output: (17n - 1n) * (19n - 1n) + }, + { + input: [[17n, 4n]] as bma.PrimeFactorization, + output: (17n ** 3n) * 16n + } + ] + for (const test of tests) { + describe(`phi([${test.input.toString()}])`, function () { + it(`should return ${test.output}`, function () { + const ret = bma.phi(test.input) + chai.expect(ret).to.equal(test.output) + }) + }) + } +})