modpow with crt; crt; modAdd; modMultiply; phi

This commit is contained in:
Juanelas 2023-06-28 16:42:24 +02:00
parent 1375c85bb5
commit fbe007f359
9 changed files with 208 additions and 0 deletions

3
build/typings/globals.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare const IS_BROWSER: boolean
declare const _MODULE_TYPE: string
declare const _NPM_PKG_VERSION: string

33
src/ts/crt.ts Normal file
View File

@ -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)
}

13
src/ts/modAdd.ts Normal file
View File

@ -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<number | bigint>, 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)
}

13
src/ts/modMultiply.ts Normal file
View File

@ -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<number | bigint>, 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)
}

13
src/ts/phi.ts Normal file
View File

@ -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)
}

50
test/crt.ts Normal file
View File

@ -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)
}
})
})
}
})

31
test/modAdd.ts Normal file
View File

@ -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))
})
})
}
})

30
test/modMultiply.ts Normal file
View File

@ -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))
})
})
}
})

22
test/phi.ts Normal file
View File

@ -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)
})
})
}
})