1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 08:33:43 -05:00

fix(ext/node): prime generation (#18861)

Towards https://github.com/denoland/deno/issues/18455

`safe`, `add` and `rem` options are not implemented because there is no
rust crate that provides this functionality (except rust-openssl maybe)
and its just not clear if this API is used widely.
This commit is contained in:
Divy Srivastava 2023-04-27 19:40:59 +05:30 committed by GitHub
parent 742cc3111c
commit b0264bea7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 256 additions and 154 deletions

View file

@ -36,129 +36,129 @@ assert(
checks: 10
}));
// (async function() {
// const prime = await pgeneratePrime(36);
// assert(await pCheckPrime(prime));
// })().then(common.mustCall());
(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(() => {
generatePrimeSync(32, { bigint: '' });
}, { code: 'ERR_INVALID_ARG_TYPE' });
// assert.throws(() => {
// generatePrime(32, { bigint: '' }, common.mustNotCall());
// }, { 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));
// }
{
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));
// }));
// }
{
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, {}, []].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, 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'
// });
// });
['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/
// });
// });
[-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'
// });
// });
['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.
{
// 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, { 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(() => 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'
// });
// }
// 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);
// }));
// }));
generatePrime(80, common.mustSucceed((prime) => {
assert(checkPrimeSync(prime));
checkPrime(prime, common.mustSucceed((result) => {
assert(result);
}));
}));
// assert(checkPrimeSync(generatePrimeSync(80)));
assert(checkPrimeSync(generatePrimeSync(80)));
// generatePrime(80, {}, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));
// }));
generatePrime(80, {}, common.mustSucceed((prime) => {
assert(checkPrimeSync(prime));
}));
// assert(checkPrimeSync(generatePrimeSync(80, {})));
assert(checkPrimeSync(generatePrimeSync(80, {})));
// generatePrime(32, { safe: true }, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime));

View file

@ -213,6 +213,8 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_check_prime_async,
ops::crypto::op_node_check_prime_bytes,
ops::crypto::op_node_check_prime_bytes_async,
ops::crypto::op_node_gen_prime,
ops::crypto::op_node_gen_prime_async,
ops::crypto::op_node_pbkdf2,
ops::crypto::op_node_pbkdf2_async,
ops::crypto::op_node_hkdf,

View file

@ -901,3 +901,20 @@ pub async fn op_node_scrypt_async(
})
.await?
}
#[inline]
fn gen_prime(size: usize) -> ZeroCopyBuf {
primes::Prime::generate(size).0.to_bytes_be().into()
}
#[op]
pub fn op_node_gen_prime(size: usize) -> ZeroCopyBuf {
gen_prime(size)
}
#[op]
pub async fn op_node_gen_prime_async(
size: usize,
) -> Result<ZeroCopyBuf, AnyError> {
Ok(tokio::task::spawn_blocking(move || gen_prime(size)).await?)
}

View file

@ -8,7 +8,7 @@ use num_traits::Zero;
use rand::Rng;
use std::ops::Deref;
pub struct Prime(num_bigint_dig::BigUint);
pub struct Prime(pub num_bigint_dig::BigUint);
impl Prime {
pub fn generate(n: usize) -> Self {

View file

@ -8,6 +8,7 @@ import randomFill, {
} from "ext:deno_node/internal/crypto/_randomFill.ts";
import randomInt from "ext:deno_node/internal/crypto/_randomInt.ts";
import {
validateBoolean,
validateFunction,
validateInt32,
validateObject,
@ -16,7 +17,10 @@ import {
isAnyArrayBuffer,
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
import {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
} from "ext:deno_node/internal/errors.ts";
export { default as randomBytes } from "ext:deno_node/internal/crypto/_randomBytes.ts";
export {
@ -142,62 +146,141 @@ export interface GeneratePrimeOptions {
bigint?: boolean | undefined;
}
export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions {
bigint: true;
}
export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions {
bigint?: false | undefined;
}
export function generatePrime(
size: number,
callback: (err: Error | null, prime: ArrayBuffer) => void,
): void;
export function generatePrime(
size: number,
options: GeneratePrimeOptionsBigInt,
callback: (err: Error | null, prime: bigint) => void,
): void;
export function generatePrime(
size: number,
options: GeneratePrimeOptionsArrayBuffer,
callback: (err: Error | null, prime: ArrayBuffer) => void,
): void;
export function generatePrime(
size: number,
options: GeneratePrimeOptions,
callback: (err: Error | null, prime: ArrayBuffer | bigint) => void,
): void;
export function generatePrime(
_size: number,
_options?: unknown,
_callback?: unknown,
options: GeneratePrimeOptions = {},
callback?: (err: Error | null, prime: ArrayBuffer | bigint) => void,
) {
notImplemented("crypto.generatePrime");
validateInt32(size, "size", 1);
if (typeof options === "function") {
callback = options;
options = {};
}
validateFunction(callback, "callback");
const {
bigint,
} = validateRandomPrimeJob(size, options);
core.opAsync2("op_node_gen_prime_async", size).then((prime: Uint8Array) =>
bigint ? arrayBufferToUnsignedBigInt(prime.buffer) : prime.buffer
).then((prime: ArrayBuffer | bigint) => {
callback?.(null, prime);
});
}
export function generatePrimeSync(size: number): ArrayBuffer;
export function generatePrimeSync(
size: number,
options: GeneratePrimeOptionsBigInt,
): bigint;
export function generatePrimeSync(
size: number,
options: GeneratePrimeOptionsArrayBuffer,
): ArrayBuffer;
export function generatePrimeSync(
options: GeneratePrimeOptions = {},
): ArrayBuffer | bigint {
const {
bigint,
} = validateRandomPrimeJob(size, options);
const prime = ops.op_node_gen_prime(size);
if (bigint) return arrayBufferToUnsignedBigInt(prime.buffer);
return prime.buffer;
}
function validateRandomPrimeJob(
size: number,
options: GeneratePrimeOptions,
): ArrayBuffer | bigint;
export function generatePrimeSync(
_size: number,
_options?:
| GeneratePrimeOptionsBigInt
| GeneratePrimeOptionsArrayBuffer
| GeneratePrimeOptions,
): ArrayBuffer | bigint {
notImplemented("crypto.generatePrimeSync");
): GeneratePrimeOptions {
validateInt32(size, "size", 1);
validateObject(options, "options");
let {
safe = false,
bigint = false,
add,
rem,
} = options!;
validateBoolean(safe, "options.safe");
validateBoolean(bigint, "options.bigint");
if (add !== undefined) {
if (typeof add === "bigint") {
add = unsignedBigIntToBuffer(add, "options.add");
} else if (!isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
throw new ERR_INVALID_ARG_TYPE(
"options.add",
[
"ArrayBuffer",
"TypedArray",
"Buffer",
"DataView",
"bigint",
],
add,
);
}
}
if (rem !== undefined) {
if (typeof rem === "bigint") {
rem = unsignedBigIntToBuffer(rem, "options.rem");
} else if (!isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
throw new ERR_INVALID_ARG_TYPE(
"options.rem",
[
"ArrayBuffer",
"TypedArray",
"Buffer",
"DataView",
"bigint",
],
rem,
);
}
}
// TODO(@littledivy): safe, add and rem options are not implemented.
if (safe || add || rem) {
notImplemented("safe, add and rem options are not implemented.");
}
return {
safe,
bigint,
add,
rem,
};
}
/**
* 48 is the ASCII code for '0', 97 is the ASCII code for 'a'.
* @param {number} number An integer between 0 and 15.
* @returns {number} corresponding to the ASCII code of the hex representation
* of the parameter.
*/
const numberToHexCharCode = (number: number): number =>
(number < 10 ? 48 : 87) + number;
/**
* @param {ArrayBuffer} buf An ArrayBuffer.
* @return {bigint}
*/
function arrayBufferToUnsignedBigInt(buf: ArrayBuffer): bigint {
const length = buf.byteLength;
const chars: number[] = Array(length * 2);
const view = new DataView(buf);
for (let i = 0; i < length; i++) {
const val = view.getUint8(i);
chars[2 * i] = numberToHexCharCode(val >> 4);
chars[2 * i + 1] = numberToHexCharCode(val & 0xf);
}
return BigInt(`0x${String.fromCharCode(...chars)}`);
}
function unsignedBigIntToBuffer(bigint: bigint, name: string) {
if (bigint < 0) {
throw new ERR_OUT_OF_RANGE(name, ">= 0", bigint);
}
const hex = bigint.toString(16);
const padded = hex.padStart(hex.length + (hex.length % 2), 0);
return Buffer.from(padded, "hex");
}
export const randomUUID = () => globalThis.crypto.randomUUID();