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 checks: 10
})); }));
// (async function() { (async function() {
// const prime = await pgeneratePrime(36); const prime = await pgeneratePrime(36);
// assert(await pCheckPrime(prime)); assert(await pCheckPrime(prime));
// })().then(common.mustCall()); })().then(common.mustCall());
// assert.throws(() => { assert.throws(() => {
// generatePrimeSync(32, { bigint: '' }); generatePrimeSync(32, { bigint: '' });
// }, { code: 'ERR_INVALID_ARG_TYPE' }); }, { code: 'ERR_INVALID_ARG_TYPE' });
// assert.throws(() => { assert.throws(() => {
// generatePrime(32, { bigint: '' }, common.mustNotCall()); generatePrime(32, { bigint: '' }, common.mustNotCall());
// }, { code: 'ERR_INVALID_ARG_TYPE' }); }, { code: 'ERR_INVALID_ARG_TYPE' });
// { {
// const prime = generatePrimeSync(3, { bigint: true }); const prime = generatePrimeSync(3, { bigint: true });
// assert.strictEqual(typeof prime, 'bigint'); assert.strictEqual(typeof prime, 'bigint');
// assert.strictEqual(prime, 7n); assert.strictEqual(prime, 7n);
// assert(checkPrimeSync(prime)); assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed(assert)); checkPrime(prime, common.mustSucceed(assert));
// } }
// { {
// generatePrime(3, { bigint: true }, common.mustSucceed((prime) => { generatePrime(3, { bigint: true }, common.mustSucceed((prime) => {
// assert.strictEqual(typeof prime, 'bigint'); assert.strictEqual(typeof prime, 'bigint');
// assert.strictEqual(prime, 7n); assert.strictEqual(prime, 7n);
// assert(checkPrimeSync(prime)); assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed(assert)); checkPrime(prime, common.mustSucceed(assert));
// })); }));
// } }
// ['hello', false, {}, []].forEach((i) => { ['hello', false, {}, []].forEach((i) => {
// assert.throws(() => generatePrime(i), { assert.throws(() => generatePrime(i), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrimeSync(i), { assert.throws(() => generatePrimeSync(i), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// }); });
// ['hello', false, 123].forEach((i) => { ['hello', false, 123].forEach((i) => {
// assert.throws(() => generatePrime(80, i, common.mustNotCall()), { assert.throws(() => generatePrime(80, i, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrimeSync(80, i), { assert.throws(() => generatePrimeSync(80, i), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// }); });
// ['hello', false, 123].forEach((i) => { ['hello', false, 123].forEach((i) => {
// assert.throws(() => generatePrime(80, {}), { assert.throws(() => generatePrime(80, {}), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// }); });
// [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => { [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => {
// assert.throws(() => generatePrime(size, common.mustNotCall()), { assert.throws(() => generatePrime(size, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE', code: 'ERR_OUT_OF_RANGE',
// message: />= 1 && <= 2147483647/ message: />= 1 && <= 2147483647/
// }); });
// assert.throws(() => generatePrimeSync(size), { assert.throws(() => generatePrimeSync(size), {
// code: 'ERR_OUT_OF_RANGE', code: 'ERR_OUT_OF_RANGE',
// message: />= 1 && <= 2147483647/ message: />= 1 && <= 2147483647/
// }); });
// }); });
// ['test', -1, {}, []].forEach((i) => { ['test', -1, {}, []].forEach((i) => {
// assert.throws(() => generatePrime(8, { safe: i }, common.mustNotCall()), { assert.throws(() => generatePrime(8, { safe: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrime(8, { rem: i }, common.mustNotCall()), { assert.throws(() => generatePrime(8, { rem: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrime(8, { add: i }, common.mustNotCall()), { assert.throws(() => generatePrime(8, { add: i }, common.mustNotCall()), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrimeSync(8, { safe: i }), { assert.throws(() => generatePrimeSync(8, { safe: i }), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrimeSync(8, { rem: i }), { assert.throws(() => generatePrimeSync(8, { rem: i }), {
// code: 'ERR_INVALID_ARG_TYPE' code: 'ERR_INVALID_ARG_TYPE'
// }); });
// assert.throws(() => generatePrimeSync(8, { add: i }), { assert.throws(() => generatePrimeSync(8, { add: i }), {
// code: 'ERR_INVALID_ARG_TYPE' 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()), { assert.throws(() => generatePrime(20, { add: -1n }, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE', code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "options.add" is out of range. It must be >= 0. ' + message: 'The value of "options.add" is out of range. It must be >= 0. ' +
// 'Received -1n' 'Received -1n'
// }); });
// assert.throws(() => generatePrime(20, { rem: -1n }, common.mustNotCall()), { assert.throws(() => generatePrime(20, { rem: -1n }, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE', code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "options.rem" is out of range. It must be >= 0. ' + message: 'The value of "options.rem" is out of range. It must be >= 0. ' +
// 'Received -1n' 'Received -1n'
// }); });
// assert.throws(() => checkPrime(-1n, common.mustNotCall()), { // assert.throws(() => checkPrime(-1n, common.mustNotCall()), {
// code: 'ERR_OUT_OF_RANGE', // code: 'ERR_OUT_OF_RANGE',
// message: 'The value of "candidate" is out of range. It must be >= 0. ' + // message: 'The value of "candidate" is out of range. It must be >= 0. ' +
// 'Received -1n' // 'Received -1n'
// }); // });
// } }
// generatePrime(80, common.mustSucceed((prime) => { generatePrime(80, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime)); assert(checkPrimeSync(prime));
// checkPrime(prime, common.mustSucceed((result) => { checkPrime(prime, common.mustSucceed((result) => {
// assert(result); assert(result);
// })); }));
// })); }));
// assert(checkPrimeSync(generatePrimeSync(80))); assert(checkPrimeSync(generatePrimeSync(80)));
// generatePrime(80, {}, common.mustSucceed((prime) => { generatePrime(80, {}, common.mustSucceed((prime) => {
// assert(checkPrimeSync(prime)); assert(checkPrimeSync(prime));
// })); }));
// assert(checkPrimeSync(generatePrimeSync(80, {}))); assert(checkPrimeSync(generatePrimeSync(80, {})));
// generatePrime(32, { safe: true }, common.mustSucceed((prime) => { // generatePrime(32, { safe: true }, common.mustSucceed((prime) => {
// assert(checkPrimeSync(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_async,
ops::crypto::op_node_check_prime_bytes, ops::crypto::op_node_check_prime_bytes,
ops::crypto::op_node_check_prime_bytes_async, 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,
ops::crypto::op_node_pbkdf2_async, ops::crypto::op_node_pbkdf2_async,
ops::crypto::op_node_hkdf, ops::crypto::op_node_hkdf,

View file

@ -901,3 +901,20 @@ pub async fn op_node_scrypt_async(
}) })
.await? .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 rand::Rng;
use std::ops::Deref; use std::ops::Deref;
pub struct Prime(num_bigint_dig::BigUint); pub struct Prime(pub num_bigint_dig::BigUint);
impl Prime { impl Prime {
pub fn generate(n: usize) -> Self { pub fn generate(n: usize) -> Self {

View file

@ -8,6 +8,7 @@ import randomFill, {
} from "ext:deno_node/internal/crypto/_randomFill.ts"; } from "ext:deno_node/internal/crypto/_randomFill.ts";
import randomInt from "ext:deno_node/internal/crypto/_randomInt.ts"; import randomInt from "ext:deno_node/internal/crypto/_randomInt.ts";
import { import {
validateBoolean,
validateFunction, validateFunction,
validateInt32, validateInt32,
validateObject, validateObject,
@ -16,7 +17,10 @@ import {
isAnyArrayBuffer, isAnyArrayBuffer,
isArrayBufferView, isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts"; } 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 { default as randomBytes } from "ext:deno_node/internal/crypto/_randomBytes.ts";
export { export {
@ -142,62 +146,141 @@ export interface GeneratePrimeOptions {
bigint?: boolean | undefined; bigint?: boolean | undefined;
} }
export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions {
bigint: true;
}
export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions {
bigint?: false | undefined;
}
export function generatePrime( export function generatePrime(
size: number, size: number,
callback: (err: Error | null, prime: ArrayBuffer) => void, options: GeneratePrimeOptions = {},
): void; callback?: (err: Error | null, prime: ArrayBuffer | bigint) => 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,
) { ) {
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( export function generatePrimeSync(
size: number, size: number,
options: GeneratePrimeOptionsBigInt, options: GeneratePrimeOptions = {},
): bigint; ): ArrayBuffer | bigint {
export function generatePrimeSync( const {
size: number, bigint,
options: GeneratePrimeOptionsArrayBuffer, } = validateRandomPrimeJob(size, options);
): ArrayBuffer;
export function generatePrimeSync( const prime = ops.op_node_gen_prime(size);
if (bigint) return arrayBufferToUnsignedBigInt(prime.buffer);
return prime.buffer;
}
function validateRandomPrimeJob(
size: number, size: number,
options: GeneratePrimeOptions, options: GeneratePrimeOptions,
): ArrayBuffer | bigint; ): GeneratePrimeOptions {
export function generatePrimeSync( validateInt32(size, "size", 1);
_size: number, validateObject(options, "options");
_options?:
| GeneratePrimeOptionsBigInt let {
| GeneratePrimeOptionsArrayBuffer safe = false,
| GeneratePrimeOptions, bigint = false,
): ArrayBuffer | bigint { add,
notImplemented("crypto.generatePrimeSync"); 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(); export const randomUUID = () => globalThis.crypto.randomUUID();