D
Deno

help

How do I make the crypto.subtle.digest algo flexible?

QQuantum11/14/2023
This is a learning moment, bear with me. ๐Ÿ˜… I can hard code an algo for the digest like this:
const algo = "SHA-256";
const d = new Uint8Array(
await crypto.subtle.digest(
algo, new TextEncoder().encode("test")
)
);
const algo = "SHA-256";
const d = new Uint8Array(
await crypto.subtle.digest(
algo, new TextEncoder().encode("test")
)
);
But if I want to make the algo flexible, it forces me to make it a DigestAlgorithm and it cannot be a string shown here:
async function digest(algo: string): Promise<Uint8Array> {
return new Uint8Array(
await crypto.subtle.digest(
algo, new TextEncoder().encode("test")
)
);
}
async function digest(algo: string): Promise<Uint8Array> {
return new Uint8Array(
await crypto.subtle.digest(
algo, new TextEncoder().encode("test")
)
);
}
I reviewed what that is in the source: https://github.com/denoland/deno_std/blob/main/crypto/_wasm/mod.ts I see this: export type DigestAlgorithm = typeof digestAlgorithms[number]; (I don't quite see what is going on here and am interested.) Bottom line is, when I make algo:string a algo:DigestAlgorithm in the digest function param list, it stops complaining, but I need to have some calling code somewhere be a flexible string (or maybe an enum if necessary) that somehow translates to a DigestAlgorithm for this to work. I've been struggling with this. Any ideas?
Mmarvinh.11/14/2023
That's because not any random string is a valid algorithm. The string asdf for example is not a valid algorithm for the crypto module But typing it as string would mean that this would be valid, which leads to a type conflict the type string is too wide only particular strings are valid, and those are defined in the digestAlgorithms array
Ccknight11/14/2023
DigestAlgorithm is still a string, just a very specific string from one of the digestAlgorithms. E.g.
export const digestAlgorithms = [
"BLAKE2B-128",
"BLAKE2B-224",
"BLAKE2B-256",
"BLAKE2B-384",
"BLAKE2B",
"BLAKE2S",
"BLAKE3",
"KECCAK-224",
"KECCAK-256",
"KECCAK-384",
"KECCAK-512",
"SHA-384",
"SHA3-224",
"SHA3-256",
"SHA3-384",
"SHA3-512",
"SHAKE128",
"SHAKE256",
"TIGER",
// insecure (length-extendable):
"RIPEMD-160",
"SHA-224",
"SHA-256",
"SHA-512",
// insecure (collidable and length-extendable):
"MD4",
"MD5",
"SHA-1",
]
export const digestAlgorithms = [
"BLAKE2B-128",
"BLAKE2B-224",
"BLAKE2B-256",
"BLAKE2B-384",
"BLAKE2B",
"BLAKE2S",
"BLAKE3",
"KECCAK-224",
"KECCAK-256",
"KECCAK-384",
"KECCAK-512",
"SHA-384",
"SHA3-224",
"SHA3-256",
"SHA3-384",
"SHA3-512",
"SHAKE128",
"SHAKE256",
"TIGER",
// insecure (length-extendable):
"RIPEMD-160",
"SHA-224",
"SHA-256",
"SHA-512",
// insecure (collidable and length-extendable):
"MD4",
"MD5",
"SHA-1",
]
This prevents your code from using an unsupported (or nonsense) algorithm.
QQuantum11/14/2023
I agree and understand why. My goal is to understand how to make it flexible. Am I forced to hard code this value?
Ccknight11/14/2023
By hard code, do you mean digestAlgorithms? No, you simply import DigestAlgorithm which is a type matching one of the above constants. What is this code for? A library? A web app? Can you provide more context? This is as flexible as possible:
/* Your library or module, "my_modules.ts" */
import { DigestAlgorithm } from "https://deno.land/std@0.206.0/crypto/mod.ts";
export async function digest(algo: DigestAlgorithm, content:string): Promise<Uint8Array> { ... }

/* In Consumer A module */
import {digest} from "./my_module.ts";
await digest("SHA-1", content);

/* In Consumer B module */
import {digest} from "./my_module.ts";
await digest("SHA-256", content);
/* Your library or module, "my_modules.ts" */
import { DigestAlgorithm } from "https://deno.land/std@0.206.0/crypto/mod.ts";
export async function digest(algo: DigestAlgorithm, content:string): Promise<Uint8Array> { ... }

/* In Consumer A module */
import {digest} from "./my_module.ts";
await digest("SHA-1", content);

/* In Consumer B module */
import {digest} from "./my_module.ts";
await digest("SHA-256", content);
and trying this gives a compile error when using Typescript:
/* In Consumer C module */
import {digest} from "./my_module.ts";
await digest("abcd", content); //Typescript complains since "abcd" is not of type DigestAlgorithm
/* In Consumer C module */
import {digest} from "./my_module.ts";
await digest("abcd", content); //Typescript complains since "abcd" is not of type DigestAlgorithm
QQuantum11/15/2023
@cknight I am writing the beginnings of a deterministic pseudo random library that allows the user of the library to specify what algorithm to use. It would be a separate issue to discuss the merits of such a library. (Very happy to discuss!) The best that I can do to make this flexible is by doing the following:
import { crypto, DigestAlgorithm } from "https://deno.land/std@0.202.0/crypto/mod.ts";

export default class Random {
algo: DigestAlgorithm;
index: number;
random256!: Uint8Array;

constructor(algo: DigestAlgorithm) {
this.algo = algo;
this.index = 0;
}

static async new({algo, seed}: {algo: string, seed: string}): Promise<Random> {
let da: DigestAlgorithm = "BLAKE2B"; //default
if (algo == "BLAKE2B-224") da = "BLAKE2B-224";
if (algo == "BLAKE2B-256") da = "BLAKE2B-256";
if (algo == "BLAKE2B-384") da = "BLAKE2B-384";
if (algo == "BLAKE2B") da = "BLAKE2B";
if (algo == "BLAKE2S") da = "BLAKE2S";
if (algo == "BLAKE3") da = "BLAKE3";
if (algo == "KECCAK-224") da = "KECCAK-224";
if (algo == "KECCAK-256") da = "KECCAK-256";
if (algo == "KECCAK-384") da = "KECCAK-384";
if (algo == "KECCAK-512") da = "KECCAK-512";
if (algo == "SHA-384") da = "SHA-384";
if (algo == "SHA3-224") da = "SHA3-224";
if (algo == "SHA3-256") da = "SHA3-256";
if (algo == "SHA3-384") da = "SHA3-384";
if (algo == "SHA3-512") da = "SHA3-512";
if (algo == "SHAKE128") da = "SHAKE128";
if (algo == "SHAKE256") da = "SHAKE256";
if (algo == "TIGER") da = "TIGER";
if (algo == "RIPEMD-160") da = "RIPEMD-160";
if (algo == "SHA-224") da = "SHA-224";
if (algo == "SHA-256") da = "SHA-256";
if (algo == "SHA-512") da = "SHA-512";
if (algo == "MD4") da = "MD4";
if (algo == "MD5") da = "MD5";
if (algo == "SHA-1") da = "SHA-1";

const random = new Random(da);

random.random256 = new Uint8Array(
await crypto.subtle.digest(
random.algo, new TextEncoder().encode(seed)
)
);

return random;
}
}
import { crypto, DigestAlgorithm } from "https://deno.land/std@0.202.0/crypto/mod.ts";

export default class Random {
algo: DigestAlgorithm;
index: number;
random256!: Uint8Array;

constructor(algo: DigestAlgorithm) {
this.algo = algo;
this.index = 0;
}

static async new({algo, seed}: {algo: string, seed: string}): Promise<Random> {
let da: DigestAlgorithm = "BLAKE2B"; //default
if (algo == "BLAKE2B-224") da = "BLAKE2B-224";
if (algo == "BLAKE2B-256") da = "BLAKE2B-256";
if (algo == "BLAKE2B-384") da = "BLAKE2B-384";
if (algo == "BLAKE2B") da = "BLAKE2B";
if (algo == "BLAKE2S") da = "BLAKE2S";
if (algo == "BLAKE3") da = "BLAKE3";
if (algo == "KECCAK-224") da = "KECCAK-224";
if (algo == "KECCAK-256") da = "KECCAK-256";
if (algo == "KECCAK-384") da = "KECCAK-384";
if (algo == "KECCAK-512") da = "KECCAK-512";
if (algo == "SHA-384") da = "SHA-384";
if (algo == "SHA3-224") da = "SHA3-224";
if (algo == "SHA3-256") da = "SHA3-256";
if (algo == "SHA3-384") da = "SHA3-384";
if (algo == "SHA3-512") da = "SHA3-512";
if (algo == "SHAKE128") da = "SHAKE128";
if (algo == "SHAKE256") da = "SHAKE256";
if (algo == "TIGER") da = "TIGER";
if (algo == "RIPEMD-160") da = "RIPEMD-160";
if (algo == "SHA-224") da = "SHA-224";
if (algo == "SHA-256") da = "SHA-256";
if (algo == "SHA-512") da = "SHA-512";
if (algo == "MD4") da = "MD4";
if (algo == "MD5") da = "MD5";
if (algo == "SHA-1") da = "SHA-1";

const random = new Random(da);

random.random256 = new Uint8Array(
await crypto.subtle.digest(
random.algo, new TextEncoder().encode(seed)
)
);

return random;
}
}
I think I was hoping Deno and/or TypeScript would allow for something like the following so that there is no need for all the "if" statements, which is error prone and tedious. More importantly, whenever Deno adds a new one to DigestAlgorithm (they added "BLAKE2B-128" 2 months ago), I would also need to change the code to add another "if" statement. What I was hoping for is something kind of like this:
if (DigestAlgorithm.includes(algo)) {
da = algo;
}
if (DigestAlgorithm.includes(algo)) {
da = algo;
}
If something along those lines can't be accomplished, I'm seeing this as a wider language issue. DigestAlgorithm is just an example. The same problem would come up elsewhere.
Mmarvinh.11/15/2023
You don't need all these if statements if you type algo correctly. The problem is in your new method, because there it's typed as string.
class Random {
- static async new({algo, seed}: {algo: string, seed: string}): Promise<Random> {
+ static async new({algo, seed}: {algo: DigestAlgorithm, seed: string}): Promise<Random> {
}
}
class Random {
- static async new({algo, seed}: {algo: string, seed: string}): Promise<Random> {
+ static async new({algo, seed}: {algo: DigestAlgorithm, seed: string}): Promise<Random> {
}
}
QQuantum11/15/2023
That is true, but then the calling code would need to have the "if" statements.
Mmarvinh.11/15/2023
if the value is provided by the user or something you can use a type guard:
function isValidAlgo(str: string): str is DigestAlgorithm {
return DigestAlgorithm.includes(str);
}
function isValidAlgo(str: string): str is DigestAlgorithm {
return DigestAlgorithm.includes(str);
}
That way you can tell TS that this is of type DigestAlgorithm
Mmarvinh.11/15/2023
Documentation - Narrowing
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
QQuantum11/15/2023
The "str is DigestAlgorithm" is interesting and might be what allows DigestAlgorithm.includes(algo) to work. How would I implement that on my "new" method above? Something like this doesn't quite work: static async new({algo, seed}: {algo: string, seed: string}): Promise<Random>, algo is DigestAlgorithm {
Ccknight11/15/2023
Why? await Random.new({algo: "SHA-256", seed: "my seed string"}); would compile fine, but await Random.new({algo: "blah blah blah", seed: "my seed string"}); would not, if the signature is as Marvin described
QQuantum11/15/2023
Maybe the algo comes from the database or maybe it comes from user input or somewhere else. Something needs to check it against DigestAlgorithm somewhere and the best that I could see how to do that until now was with "if" statements or something. What @marvinh. seemed to indicate is there is a way to cast algo to a DigestAlgorithm in the method signature that could then be used with "DigestAlgorithm.includes", which is interesting and might be what is needed here.
Mmarvinh.11/15/2023
you should never cast types in the method signature if not necessary. Types are there to make invalid states possible. Passing any random string to your Random class is not valid, so you should really drop the algo: string type because not any string is a valid algorithm. That solves all of your problems. The casting is only necessary if you receive the value from untrusted input like from an http request or something. Those kinds of type guards/assertions should only live at the outer edge of your program or library.
QQuantum11/15/2023
Yes, I understand and agree with casting being necessary when input is coming from untrusted source and should only live at outer edge of program or library. The new method is the outer edge of the library where the input comes in. I am attempting to work in the DigestAlgorithm.includes(algo) that you showed above, but I am getting this error here: 'DigestAlgorithm' only refers to a type, but is being used as a value here.deno-ts(2693) If it is recommended that the new method here only has algo as DigestAlgorithm, I could abide by that too, ok. But in any case, I'm getting that issue when attempting to use includes on DigestAlgorithm.
Mmarvinh.11/15/2023
Ah looks like that's only a type in the std lib
QQuantum11/15/2023
One way or another, I am hoping to get away from needing to have all those "if" statements and I am more than happy to adhere to best practices that you are sharing with me.
Mmarvinh.11/15/2023
In that case use the type guard. Here is a full example:
import { type DigestAlgorithm } from "https://deno.land/std@0.202.0/crypto/mod.ts";
import { digestAlgorithms } from "https://deno.land/std/crypto/_wasm/mod.ts";

// deno-lint-ignore no-explicit-any
function isValidAlgorithm(str: any): str is DigestAlgorithm {
return digestAlgorithms.includes(str);
}

export default class Random {
algo: DigestAlgorithm;
index: number;
random256!: Uint8Array;

constructor(algo: DigestAlgorithm) {
this.algo = algo;
this.index = 0;
}

static async new({
algo,
seed,
}: {
algo: string;
seed: string;
}): Promise<Random> {
let da: DigestAlgorithm = "BLAKE2B";
if (isValidAlgorithm(algo)) {
da = algo;
}

const random = new Random(da);

random.random256 = new Uint8Array(
await crypto.subtle.digest(random.algo, new TextEncoder().encode(seed))
);

return random;
}
}
import { type DigestAlgorithm } from "https://deno.land/std@0.202.0/crypto/mod.ts";
import { digestAlgorithms } from "https://deno.land/std/crypto/_wasm/mod.ts";

// deno-lint-ignore no-explicit-any
function isValidAlgorithm(str: any): str is DigestAlgorithm {
return digestAlgorithms.includes(str);
}

export default class Random {
algo: DigestAlgorithm;
index: number;
random256!: Uint8Array;

constructor(algo: DigestAlgorithm) {
this.algo = algo;
this.index = 0;
}

static async new({
algo,
seed,
}: {
algo: string;
seed: string;
}): Promise<Random> {
let da: DigestAlgorithm = "BLAKE2B";
if (isValidAlgorithm(algo)) {
da = algo;
}

const random = new Random(da);

random.random256 = new Uint8Array(
await crypto.subtle.digest(random.algo, new TextEncoder().encode(seed))
);

return random;
}
}
QQuantum11/15/2023
Oh nice! I wasn't even looking at the lower cased digestAlgorithms that allows you to call includes on it. Yes, your guard approach is excellent and works nicely. Thanks for this and the insights!

Looking for more? Join the community!

Recommended Posts
whatโ€™s the current best practice for bundling?Is it deno esbuild? Wondering what supports npm specifiers and bundling atmHow do I get a d.ts file to be used?I'm extending a type from `hono` by adding that code in a `hono.d.ts` file in my `src` folder. ```โ€œLog streaming APIโ€ on Deno DeployWhat does โ€œLog streaming APIโ€ refers to in the analytics section of the pricing page of Deno Deploy?Read-only FS detectionHi, I have few deps that by default create cache/log dirs... I can disable it, but the detection itsIs there a built-in parser for the string that Deno.inspect produces?I found out in another thread that Deno.inspect(myObj) produces a visually gorgeous string representHow to get the nice Deno console.log format into a string?When I do a console.log on an instantiated class object, I get a beautiful format in the terminal, tbyonm - force use of DenoWhen I use byonm enabled in my deno.json, and say I have a vite project triggered from a package.jsoCan I disable deno linting for individual files, or a path?I'm using VSCode and the Deno extension. I want to use the inbuilt functionality of VSCode for type Rust run JS/TSHow can I embed this project into a rust application so that rust can run js ts and has runtime api Set-Cookie - Header shows as "deleted"I am trying to use deno to script/scrape a website. I can successfully log in to the site (using fetI keep running into python download failed while trying to run 'cargo test' commandI am currently following the steps I. Deno's Contributing Guide and I am running cargo test but I kehow I can create an restful api with deno and firebase as database?how I can create an restful api with deno and firebase as database?How can I replace writeAllSync with the Streams API?How would one best refactor the following code to use the Web Streams API as suggested by the deprecIs possible to run nodejs (typescript) project with esm modules?I'm trying to run small nodejs project with esm modules. Simple example ```ts // foo.ts import { bAny tutorial links to help me understand the Schedule string in Deno.cron()I am currently working on an issue on the deno repo and I am trying to reconstruct the deno scheduleDeno Deploy failed:Uncaught Exception /Permission Denied : Deno.exit() is not allowedI have a college assignments that needs to be deployed using deno deploy but i get this message whenConnect to a WebSocket through a HTTP(S)/SOCKS5 proxyTitle - I am trying to connect to a `wss://` websocket through a proxy of choice at runtime. Is therExternal package code throws error: window is undefinedHello, I have imported an npm package to my project. And in this package's code, there is a conditiError with importsI have this fancy peace of code that import a bunch of files from a directory: ```ts const utils = Deno.env via fileHow can I use set env from a file and get them via deno.env.get() function as i want to push my code