Skip to content

Commit

Permalink
refactor(node/crypto): port polyfill to Rust for randomInt, randomFil…
Browse files Browse the repository at this point in the history
…l, randomFillSync (#18658)

Pretty much as per the title, I'd welcome some feedback especially
around the
array/buffer handling in the two randomFill functions.
  • Loading branch information
levex committed Apr 12, 2023
1 parent 0014ebb commit a72396f
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 18 deletions.
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

0 comments on commit a72396f

Please sign in to comment.