Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(node/crypto): port polyfill to Rust for randomInt, randomFill, randomFillSync #18658

Merged
merged 1 commit into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cli/tests/unit_node/internal/_randomFill_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
assertNotEquals,
assertThrows,
} from "../../../../test_util/std/testing/asserts.ts";
import { deferred } from "../../../../test_util/std/async/deferred.ts";

const validateNonZero = (buf: Buffer) => {
if (!buf.some((ch) => ch > 0)) throw new Error("Error");
Expand All @@ -15,14 +16,18 @@ const validateZero = (buf: Buffer) => {
buf.forEach((val) => assertEquals(val, 0));
};

Deno.test("[node/crypto.randomFill]", () => {
Deno.test("[node/crypto.randomFill]", async () => {
const promise = deferred();
const buf = Buffer.alloc(10);
const before = buf.toString("hex");

randomFill(buf, 5, 5, (_err, bufTwo) => {
const after = bufTwo?.toString("hex");
assertEquals(before.slice(0, 10), after?.slice(0, 10));
promise.resolve(true);
});

await promise;
});

Deno.test("[node/crypto.randomFillSync]", () => {
Expand Down
12 changes: 12 additions & 0 deletions ext/node/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use deno_core::StringOrBuffer;
use deno_core::ZeroCopyBuf;
use hkdf::Hkdf;
use num_bigint::BigInt;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rand::Rng;
use std::future::Future;
use std::rc::Rc;
Expand Down Expand Up @@ -478,3 +480,13 @@ pub async fn op_node_hkdf_async(
})
.await?
}

#[op]
pub fn op_node_random_int(min: i32, max: i32) -> Result<i32, AnyError> {
let mut rng = rand::thread_rng();
// Uniform distribution is required to avoid Modulo Bias
// https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#Modulo_bias
let dist = Uniform::from(min..max);

Ok(dist.sample(&mut rng))
}
1 change: 1 addition & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ deno_core::extension!(deno_node,
crypto::op_node_generate_secret,
crypto::op_node_generate_secret_async,
crypto::op_node_sign,
crypto::op_node_random_int,
crypto::x509::op_node_x509_parse,
crypto::x509::op_node_x509_ca,
crypto::x509::op_node_x509_check_email,
Expand Down
24 changes: 15 additions & 9 deletions ext/node/polyfills/internal/crypto/_randomFill.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import randomBytes, {
import {
MAX_SIZE as kMaxUint32,
} from "ext:deno_node/internal/crypto/_randomBytes.ts";
import { Buffer } from "ext:deno_node/buffer.ts";
const { core } = globalThis.__bootstrap;
const { ops } = core;

const kBufferMaxLength = 0x7fffffff;

Expand Down Expand Up @@ -62,11 +64,14 @@ export default function randomFill(
assertOffset(offset as number, buf.length);
assertSize(size as number, offset as number, buf.length);

randomBytes(size as number, (err, bytes) => {
if (err) return cb!(err, buf);
bytes?.copy(buf, offset as number);
cb!(null, buf);
});
core.opAsync("op_node_generate_secret_async", Math.floor(size as number))
.then(
(randomData: Uint8Array) => {
const randomBuf = Buffer.from(randomData.buffer);
randomBuf.copy(buf, offset as number, 0, size as number);
cb!(null, buf);
},
);
}

export function randomFillSync(buf: Buffer, offset = 0, size?: number) {
Expand All @@ -76,9 +81,10 @@ export function randomFillSync(buf: Buffer, offset = 0, size?: number) {

assertSize(size, offset, buf.length);

const bytes = randomBytes(size);

bytes.copy(buf, offset);
const bytes: Uint8Array = new Uint8Array(Math.floor(size));
ops.op_node_generate_secret(bytes);
const bytesBuf: Buffer = Buffer.from(bytes.buffer);
bytesBuf.copy(buf, offset, 0, size);

return buf;
}
11 changes: 3 additions & 8 deletions ext/node/polyfills/internal/crypto/_randomInt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
const ops = globalThis.Deno.core.ops;

export default function randomInt(max: number): number;
export default function randomInt(min: number, max: number): number;
export default function randomInt(
Expand Down Expand Up @@ -40,16 +42,9 @@ export default function randomInt(
throw new Error("Min is bigger than Max!");
}

const randomBuffer = new Uint32Array(1);

globalThis.crypto.getRandomValues(randomBuffer);

const randomNumber = randomBuffer[0] / (0xffffffff + 1);

min = Math.ceil(min);
max = Math.floor(max);

const result = Math.floor(randomNumber * (max - min)) + min;
const result = ops.op_node_random_int(min, max);

if (cb) {
cb(null, result);
Expand Down