diff --git a/cli/tests/node_compat/test/parallel/test-crypto-prime.js b/cli/tests/node_compat/test/parallel/test-crypto-prime.js index de1e88fd76813e..fc2218c2ab1529 100644 --- a/cli/tests/node_compat/test/parallel/test-crypto-prime.js +++ b/cli/tests/node_compat/test/parallel/test-crypto-prime.js @@ -36,129 +36,129 @@ assert( 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)); -// } +(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)); -// })); -// } +{ + 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)); diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 53b4f5c08dfb76..cef92328d99d28 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -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, diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index d224b40f7218bd..92e3029e0da240 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -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 { + Ok(tokio::task::spawn_blocking(move || gen_prime(size)).await?) +} diff --git a/ext/node/ops/crypto/primes.rs b/ext/node/ops/crypto/primes.rs index d03398f024b482..15aa643adbc795 100644 --- a/ext/node/ops/crypto/primes.rs +++ b/ext/node/ops/crypto/primes.rs @@ -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 { diff --git a/ext/node/polyfills/internal/crypto/random.ts b/ext/node/polyfills/internal/crypto/random.ts index 04678b6be1b2f4..32256b13bfd81d 100644 --- a/ext/node/polyfills/internal/crypto/random.ts +++ b/ext/node/polyfills/internal/crypto/random.ts @@ -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();