Skip to content

Commit

Permalink
feat(ext/node): add crypto.checkPrime API (denoland#18465)
Browse files Browse the repository at this point in the history
Towards denoland#18455 

This commit implements `checkPrimeSync` and `checkPrime` in node:crypto
using the Miller-Rabin primality test (fun fact: it actually is a test
for composite numbers)

It first compares the candidate against many known small primes and if
not, proceeds to run the Miller-Rabin primality test.
http:https://nickle.org/examples/miller-rabin.5c used as reference
implementation.
  • Loading branch information
littledivy committed Mar 28, 2023
1 parent 67e21e7 commit 10012c2
Show file tree
Hide file tree
Showing 9 changed files with 751 additions and 8 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ libc = "0.2.126"
log = "=0.4.17"
lsp-types = "=0.93.2" # used by tower-lsp and "proposed" feature is unstable in patch releases
notify = "=5.0.0"
num-bigint = "0.4"
num-bigint = { version = "0.4", features = ["rand"] }
once_cell = "1.17.1"
os_pipe = "=1.0.1"
parking_lot = "0.12.0"
Expand Down
1 change: 1 addition & 0 deletions cli/tests/node_compat/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
"test-console-sync-write-error.js",
"test-console-table.js",
"test-crypto-hmac.js",
"test-crypto-prime.js",
"test-dgram-close-during-bind.js",
"test-dgram-close-signal.js",
"test-diagnostics-channel-has-subscribers.js",
Expand Down
302 changes: 302 additions & 0 deletions cli/tests/node_compat/test/parallel/test-crypto-prime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.

'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');

const {
generatePrime,
generatePrimeSync,
checkPrime,
checkPrimeSync,
} = require('crypto');

const { promisify } = require('util');
const pgeneratePrime = promisify(generatePrime);
const pCheckPrime = promisify(checkPrime);

assert(!checkPrimeSync(Buffer.from([0x1])));
assert(checkPrimeSync(Buffer.from([0x2])));
assert(checkPrimeSync(Buffer.from([0x3])));
assert(!checkPrimeSync(Buffer.from([0x4])));

assert(
!checkPrimeSync(
Buffer.from([0x1]),
{
fast: true,
trialDivision: true,
checks: 10
}));

// (async function() {
// const prime = await pgeneratePrime(36);
// assert(await pCheckPrime(prime));
// })().then(common.mustCall());

// assert.throws(() => {
// generatePrimeSync(32, { bigint: '' });
// }, { code: 'ERR_INVALID_ARG_TYPE' });

// assert.throws(() => {
// generatePrime(32, { bigint: '' }, common.mustNotCall());
// }, { code: 'ERR_INVALID_ARG_TYPE' });

// {
// const prime = generatePrimeSync(3, { bigint: true });
// assert.strictEqual(typeof prime, 'bigint');
// assert.strictEqual(prime, 7n);
// assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed(assert));
// }

// {
// generatePrime(3, { bigint: true }, common.mustSucceed((prime) => {
// assert.strictEqual(typeof prime, 'bigint');
// assert.strictEqual(prime, 7n);
// assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed(assert));
// }));
// }


// ['hello', false, {}, []].forEach((i) => {
// assert.throws(() => generatePrime(i), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrimeSync(i), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// });

// ['hello', false, 123].forEach((i) => {
// assert.throws(() => generatePrime(80, i, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrimeSync(80, i), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// });

// ['hello', false, 123].forEach((i) => {
// assert.throws(() => generatePrime(80, {}), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// });

// [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => {
// assert.throws(() => generatePrime(size, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE',
// message: />= 1 && <= 2147483647/
// });
// assert.throws(() => generatePrimeSync(size), {
// code: 'ERR_OUT_OF_RANGE',
// message: />= 1 && <= 2147483647/
// });
// });

// ['test', -1, {}, []].forEach((i) => {
// assert.throws(() => generatePrime(8, { safe: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrime(8, { rem: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrime(8, { add: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrimeSync(8, { safe: i }), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrimeSync(8, { rem: i }), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// assert.throws(() => generatePrimeSync(8, { add: i }), {
// code: 'ERR_INVALID_ARG_TYPE'
// });
// });

// {
// // Negative BigInts should not be converted to 0 silently.

// assert.throws(() => generatePrime(20, { add: -1n }, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "options.add" is out of range. It must be >= 0. ' +
// 'Received -1n'
// });

// assert.throws(() => generatePrime(20, { rem: -1n }, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "options.rem" is out of range. It must be >= 0. ' +
// 'Received -1n'
// });

// assert.throws(() => checkPrime(-1n, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "candidate" is out of range. It must be >= 0. ' +
// 'Received -1n'
// });
// }

// generatePrime(80, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed((result) => {
// assert(result);
// }));
// }));

// assert(checkPrimeSync(generatePrimeSync(80)));

// generatePrime(80, {}, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// }));

// assert(checkPrimeSync(generatePrimeSync(80, {})));

// generatePrime(32, { safe: true }, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// const buf = Buffer.from(prime);
// const val = buf.readUInt32BE();
// const check = (val - 1) / 2;
// buf.writeUInt32BE(check);
// assert(checkPrimeSync(buf));
// }));

// {
// const prime = generatePrimeSync(32, { safe: true });
// assert(checkPrimeSync(prime));
// const buf = Buffer.from(prime);
// const val = buf.readUInt32BE();
// const check = (val - 1) / 2;
// buf.writeUInt32BE(check);
// assert(checkPrimeSync(buf));
// }

// const add = 12;
// const rem = 11;
// const add_buf = Buffer.from([add]);
// const rem_buf = Buffer.from([rem]);
// generatePrime(
// 32,
// { add: add_buf, rem: rem_buf },
// common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// const buf = Buffer.from(prime);
// const val = buf.readUInt32BE();
// assert.strictEqual(val % add, rem);
// }));

// {
// const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf });
// assert(checkPrimeSync(prime));
// const buf = Buffer.from(prime);
// const val = buf.readUInt32BE();
// assert.strictEqual(val % add, rem);
// }

// {
// const prime = generatePrimeSync(32, { add: BigInt(add), rem: BigInt(rem) });
// assert(checkPrimeSync(prime));
// const buf = Buffer.from(prime);
// const val = buf.readUInt32BE();
// assert.strictEqual(val % add, rem);
// }

// {
// // The behavior when specifying only add without rem should depend on the
// // safe option.

// if (process.versions.openssl >= '1.1.1f') {
// generatePrime(128, {
// bigint: true,
// add: 5n
// }, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// assert.strictEqual(prime % 5n, 1n);
// }));

// generatePrime(128, {
// bigint: true,
// safe: true,
// add: 5n
// }, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// assert.strictEqual(prime % 5n, 3n);
// }));
// }
// }

// {
// // This is impossible because it implies (prime % 2**64) == 1 and
// // prime < 2**64, meaning prime = 1, but 1 is not prime.
// for (const add of [2n ** 64n, 2n ** 65n]) {
// assert.throws(() => {
// generatePrimeSync(64, { add });
// }, {
// code: 'ERR_OUT_OF_RANGE',
// message: 'invalid options.add'
// });
// }

// // Any parameters with rem >= add lead to an impossible condition.
// for (const rem of [7n, 8n, 3000n]) {
// assert.throws(() => {
// generatePrimeSync(64, { add: 7n, rem });
// }, {
// code: 'ERR_OUT_OF_RANGE',
// message: 'invalid options.rem'
// });
// }

// // This is possible, but not allowed. It implies prime == 7, which means that
// // we did not actually generate a random prime.
// assert.throws(() => {
// generatePrimeSync(3, { add: 8n, rem: 7n });
// }, {
// code: 'ERR_OUT_OF_RANGE'
// });

// if (process.versions.openssl >= '1.1.1f') {
// // This is possible and allowed (but makes little sense).
// assert.strictEqual(generatePrimeSync(4, {
// add: 15n,
// rem: 13n,
// bigint: true
// }), 13n);
// }
// }

[1, 'hello', {}, []].forEach((i) => {
assert.throws(() => checkPrime(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});

for (const checks of ['hello', {}, []]) {
assert.throws(() => checkPrime(2n, { checks }, common.mustNotCall()), {
code: 'ERR_INVALID_ARG_TYPE',
message: /checks/
});
assert.throws(() => checkPrimeSync(2n, { checks }), {
code: 'ERR_INVALID_ARG_TYPE',
message: /checks/
});
}

for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) {
assert.throws(() => checkPrime(2n, { checks }, common.mustNotCall()), {
code: 'ERR_OUT_OF_RANGE',
message: /<= 2147483647/
});
assert.throws(() => checkPrimeSync(2n, { checks }), {
code: 'ERR_OUT_OF_RANGE',
message: /<= 2147483647/
});
}
3 changes: 3 additions & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ indexmap.workspace = true
libz-sys = { version = "1.1.8", features = ["static"] }
md-5 = "0.10.5"
md4 = "0.10.2"
num-bigint.workspace = true
num-integer = "0.1.45"
num-traits = "0.2.14"
once_cell.workspace = true
path-clean = "=0.1.0"
pbkdf2 = "0.12.1"
Expand Down
Loading

0 comments on commit 10012c2

Please sign in to comment.