1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-20 22:34:46 -05:00
denoland-deno/ext/node/polyfills/internal/crypto/random.ts
2024-11-04 12:17:11 -05:00

313 lines
7.8 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { primordials } from "ext:core/mod.js";
import {
op_node_check_prime,
op_node_check_prime_async,
op_node_check_prime_bytes,
op_node_check_prime_bytes_async,
op_node_gen_prime,
op_node_gen_prime_async,
} from "ext:core/ops";
const {
StringPrototypePadStart,
StringPrototypeToString,
} = primordials;
import { notImplemented } from "ext:deno_node/_utils.ts";
import randomBytes from "ext:deno_node/internal/crypto/_randomBytes.ts";
import randomFill, {
randomFillSync,
} from "ext:deno_node/internal/crypto/_randomFill.mjs";
import randomInt from "ext:deno_node/internal/crypto/_randomInt.ts";
import {
validateBoolean,
validateFunction,
validateInt32,
validateObject,
} from "ext:deno_node/internal/validators.mjs";
import {
isAnyArrayBuffer,
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
import {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
} from "ext:deno_node/internal/errors.ts";
import { Buffer } from "node:buffer";
export { default as randomBytes } from "ext:deno_node/internal/crypto/_randomBytes.ts";
export {
default as randomFill,
randomFillSync,
} from "ext:deno_node/internal/crypto/_randomFill.mjs";
export { default as randomInt } from "ext:deno_node/internal/crypto/_randomInt.ts";
export type LargeNumberLike =
| ArrayBufferView
| SharedArrayBuffer
| ArrayBuffer
| bigint;
export interface CheckPrimeOptions {
/**
* The number of Miller-Rabin probabilistic primality iterations to perform.
* When the value is 0 (zero), a number of checks is used that yields a false positive rate of at most 2-64 for random input.
* Care must be used when selecting a number of checks.
* Refer to the OpenSSL documentation for the BN_is_prime_ex function nchecks options for more details.
*
* @default 0
*/
checks?: number | undefined;
}
export function checkPrime(
candidate: LargeNumberLike,
callback: (err: Error | null, result: boolean) => void,
): void;
export function checkPrime(
candidate: LargeNumberLike,
options: CheckPrimeOptions,
callback: (err: Error | null, result: boolean) => void,
): void;
export function checkPrime(
candidate: LargeNumberLike,
options: CheckPrimeOptions | ((err: Error | null, result: boolean) => void) =
{},
callback?: (err: Error | null, result: boolean) => void,
) {
if (typeof options === "function") {
callback = options;
options = {};
}
validateFunction(callback, "callback");
validateObject(options, "options");
const {
checks = 0,
} = options!;
validateInt32(checks, "options.checks", 0);
let op = op_node_check_prime_bytes_async;
if (typeof candidate === "bigint") {
op = op_node_check_prime_async;
} else if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
"candidate",
[
"ArrayBuffer",
"TypedArray",
"Buffer",
"DataView",
"bigint",
],
candidate,
);
}
op(candidate, checks).then(
(result) => {
callback?.(null, result);
},
).catch((err) => {
callback?.(err, false);
});
}
export function checkPrimeSync(
candidate: LargeNumberLike,
options: CheckPrimeOptions = {},
): boolean {
validateObject(options, "options");
const {
checks = 0,
} = options!;
validateInt32(checks, "options.checks", 0);
if (typeof candidate === "bigint") {
return op_node_check_prime(candidate, checks);
} else if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
"candidate",
[
"ArrayBuffer",
"TypedArray",
"Buffer",
"DataView",
"bigint",
],
candidate,
);
}
return op_node_check_prime_bytes(candidate, checks);
}
export interface GeneratePrimeOptions {
add?: LargeNumberLike | undefined;
rem?: LargeNumberLike | undefined;
/**
* @default false
*/
safe?: boolean | undefined;
bigint?: boolean | undefined;
}
export function generatePrime(
size: number,
options: GeneratePrimeOptions = {},
callback?: (err: Error | null, prime: ArrayBuffer | bigint) => void,
) {
validateInt32(size, "size", 1);
if (typeof options === "function") {
callback = options;
options = {};
}
validateFunction(callback, "callback");
const {
bigint,
} = validateRandomPrimeJob(size, options);
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,
options: GeneratePrimeOptions = {},
): ArrayBuffer | bigint {
const {
bigint,
} = validateRandomPrimeJob(size, options);
const prime = op_node_gen_prime(size);
if (bigint) return arrayBufferToUnsignedBigInt(prime.buffer);
return prime.buffer;
}
function validateRandomPrimeJob(
size: number,
options: GeneratePrimeOptions,
): 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 = StringPrototypeToString(bigint, 16);
const padded = StringPrototypePadStart(hex, hex.length + (hex.length % 2), 0);
return Buffer.from(padded, "hex");
}
export const randomUUID = () => globalThis.crypto.randomUUID();
export default {
checkPrime,
checkPrimeSync,
generatePrime,
generatePrimeSync,
randomUUID,
randomInt,
randomBytes,
randomFill,
randomFillSync,
};