From dbc63e20033860d65b7fb4ea8ef284ee77bdaeff Mon Sep 17 00:00:00 2001 From: Thibault Meunier Date: Thu, 4 Jun 2026 10:07:29 +0200 Subject: [PATCH 1/2] Fix RSAPBSSA modulus length Closes #56 --- src/partially_blindrsa.ts | 32 +++++++++++++++++++++++++------- src/prime.ts | 7 +++++-- test/partially_blindrsa.test.ts | 30 ++++++++++++++++++++++++++++++ test/primes.test.ts | 4 ++-- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/partially_blindrsa.ts b/src/partially_blindrsa.ts index 91de3e8..707994f 100644 --- a/src/partially_blindrsa.ts +++ b/src/partially_blindrsa.ts @@ -289,6 +289,10 @@ 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) @@ -296,12 +300,28 @@ export class PartiallyBlindRSA { // 3. while p == q, go to step 2. 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)); @@ -319,8 +339,6 @@ export class PartiallyBlindRSA { 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..3a6d33a 100644 --- a/test/partially_blindrsa.test.ts +++ b/test/partially_blindrsa.test.ts @@ -78,6 +78,36 @@ test('Parameters', () => { } }); +test.each([2, 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, From fa235132bf3f4a10c0c3081fdcbff81a030525d2 Mon Sep 17 00:00:00 2001 From: Thibault Meunier Date: Thu, 18 Jun 2026 10:24:26 +0200 Subject: [PATCH 2/2] Clarify RSAPBSSA keygen retry --- src/partially_blindrsa.ts | 2 +- test/partially_blindrsa.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/partially_blindrsa.ts b/src/partially_blindrsa.ts index 707994f..81ccdb7 100644 --- a/src/partially_blindrsa.ts +++ b/src/partially_blindrsa.ts @@ -298,6 +298,7 @@ export class PartiallyBlindRSA { // 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; @@ -338,7 +339,6 @@ export class PartiallyBlindRSA { // TODO: replace this applying Chinese Remainder Theorem. const d = inverseMod(e, phi); - // 7. n = p * q // 7. sk = (n, p, q, phi, d) const sk: BigSecretKey = { n, p, q, d }; // 8. pk = (n, e) diff --git a/test/partially_blindrsa.test.ts b/test/partially_blindrsa.test.ts index 3a6d33a..e267f4d 100644 --- a/test/partially_blindrsa.test.ts +++ b/test/partially_blindrsa.test.ts @@ -78,7 +78,7 @@ test('Parameters', () => { } }); -test.each([2, 513])('generateKey/rejects invalid modulus length/%d', async (modulusLength) => { +test.each([2, 3, 513])('generateKey/rejects invalid modulus length/%d', async (modulusLength) => { await expect( PartiallyBlindRSA.generateKey({ modulusLength,