diff --git a/src/partially_blindrsa.ts b/src/partially_blindrsa.ts index 91de3e8..81ccdb7 100644 --- a/src/partially_blindrsa.ts +++ b/src/partially_blindrsa.ts @@ -289,19 +289,40 @@ export class PartiallyBlindRSA { algorithm: Pick, generateSafePrimeSync: (length: number) => sjcl.BigNumber | bigint = generateSafePrime, ): Promise { + if (algorithm.modulusLength % 2 !== 0 || algorithm.modulusLength < 4) { + throw new Error('modulusLength must be an even number greater than or equal to 4'); + } + prepare_sjcl_random_generator(); // 1. p = SafePrime(bits / 2) // 2. q = SafePrime(bits / 2) // 3. while p == q, go to step 2. + // Also retry if their product is one bit short of the requested modulus length. let p: sjcl.BigNumber; let q: sjcl.BigNumber; + let n: sjcl.BigNumber; + const primeBitLength = algorithm.modulusLength >> 1; + const MAX_NUM_TRIES = algorithm.modulusLength ** 2; + let validKeySize = false; + let i = 0; do { - const p_tmp = generateSafePrimeSync(algorithm.modulusLength >> 1); - const q_tmp = generateSafePrimeSync(algorithm.modulusLength >> 1); - p = typeof p_tmp === 'bigint' ? new sjcl.bn(p_tmp.toString(16)) : p_tmp; - q = typeof q_tmp === 'bigint' ? new sjcl.bn(q_tmp.toString(16)) : q_tmp; - } while (p.equals(q)); + const p_tmp = generateSafePrimeSync(primeBitLength); + const q_tmp = generateSafePrimeSync(primeBitLength); + p = typeof p_tmp === 'bigint' ? new sjcl.bn('0x' + p_tmp.toString(16)) : p_tmp; + q = typeof q_tmp === 'bigint' ? new sjcl.bn('0x' + q_tmp.toString(16)) : q_tmp; + n = p.mul(q); + validKeySize = + !p.equals(q) && + p.bitLength() === primeBitLength && + q.bitLength() === primeBitLength && + n.bitLength() === algorithm.modulusLength; + i++; + } while (!validKeySize && i < MAX_NUM_TRIES); + + if (!validKeySize) { + throw new Error(`generateKey reached MAX_NUM_TRIES=${MAX_NUM_TRIES}`); + } // 4. phi = (p - 1) * (q - 1) const phi = p.sub(1).mul(q.sub(1)); @@ -318,9 +339,6 @@ export class PartiallyBlindRSA { // TODO: replace this applying Chinese Remainder Theorem. const d = inverseMod(e, phi); - // 7. n = p * q - const n = p.mul(q); - // 7. sk = (n, p, q, phi, d) const sk: BigSecretKey = { n, p, q, d }; // 8. pk = (n, e) diff --git a/src/prime.ts b/src/prime.ts index ba779f4..e76ed67 100644 --- a/src/prime.ts +++ b/src/prime.ts @@ -66,17 +66,20 @@ export function generatePrime(bitLength: number, NUM_TRIES_PRIMALITY = 20): sjcl const MAX_NUM_TRIES = NUM_TRIES_PRIMALITY * bitLength ** 4; // 2^b - const twoToN = new sjcl.bn(2); + const twoToN = new sjcl.bn(1); for (let i = 0; i < bitLength; i++) { twoToN.doubleM(); } twoToN.normalize(); + const twoToNMinusOne = new sjcl.bn(twoToN).halveM().normalize(); + let prime: sjcl.BigNumber; let i = 0; do { - prime = sjcl.bn.random(twoToN, SJCL_PARANOIA); + prime = sjcl.bn.random(twoToNMinusOne, SJCL_PARANOIA); + prime = prime.addM(twoToNMinusOne).normalize(); if ((prime.getLimb(0) & 0x1) == 0) { prime = prime.addM(1).normalize(); } diff --git a/test/partially_blindrsa.test.ts b/test/partially_blindrsa.test.ts index 44ce60a..e267f4d 100644 --- a/test/partially_blindrsa.test.ts +++ b/test/partially_blindrsa.test.ts @@ -78,6 +78,36 @@ test('Parameters', () => { } }); +test.each([2, 3, 513])('generateKey/rejects invalid modulus length/%d', async (modulusLength) => { + await expect( + PartiallyBlindRSA.generateKey({ + modulusLength, + publicExponent: Uint8Array.of(0x01, 0x00, 0x01), + hash: 'SHA-384', + }), + ).rejects.toThrow('modulusLength must be an even number greater than or equal to 4'); +}); + +test('generateKey/stops retrying invalid bigint safe-prime hook', async () => { + let primeCount = 0; + + await expect( + PartiallyBlindRSA.generateKey( + { + modulusLength: 4, + publicExponent: Uint8Array.of(0x01, 0x00, 0x01), + hash: 'SHA-384', + }, + () => { + primeCount++; + return 2n; + }, + ), + ).rejects.toThrow('generateKey reached MAX_NUM_TRIES=16'); + + expect(primeCount).toBe(32); +}); + describe.each(vectors)('Errors-vec%#', (v: Vector) => { test('non-extractable-keys', async () => { const { privateKey, publicKey } = await keysFromVector(v, false); diff --git a/test/primes.test.ts b/test/primes.test.ts index 6324749..a9ae1da 100644 --- a/test/primes.test.ts +++ b/test/primes.test.ts @@ -83,7 +83,7 @@ test.each([128, 256, 512, 1024])( (bitLength) => { const p = generatePrime(bitLength); - expect(p.bitLength()).toBeGreaterThanOrEqual(bitLength); + expect(p.bitLength()).toBe(bitLength); expect(isPrime(p)).toBe(true); }, 1_200_000, @@ -94,7 +94,7 @@ test.each([128, 256])( (bitLength) => { const p = generateSafePrime(bitLength); - expect(p.bitLength()).toBeGreaterThanOrEqual(bitLength); + expect(p.bitLength()).toBe(bitLength); expect(isSafePrime(p)).toBe(true); }, 1_200_000,