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

perf!(hash): use node:crypto when possible #100

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
Next Next commit
wip
pi0 committed Jan 13, 2025
commit 11a5a9e91e0d709e0e342dfe32f569f90f15acc0
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -11,6 +11,16 @@
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs"
},
"./crypto": {
"node": {
"import": "./dist/crypto/node.mjs",
"require": "./dist/crypto/node.cjs"
},
"default": {
"import": "./dist/crypto/js.mjs",
"require": "./dist/crypto/js.cjs"
}
}
},
"main": "./dist/index.cjs",
16 changes: 8 additions & 8 deletions src/crypto/core.ts → src/crypto/js/_core.ts
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@ export class WordArray {
// Copy one byte at a time
for (let i = 0; i < wordArray.sigBytes; i++) {
const thatByte =
(wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
this.words[(this.sigBytes + i) >>> 2] |=
(wordArray.words[i >>> 2]! >>> (24 - (i % 4) * 8)) & 0xff;
this.words[(this.sigBytes + i) >>> 2]! |=
thatByte << (24 - ((this.sigBytes + i) % 4) * 8);
}
} else {
@@ -41,7 +41,7 @@ export class WordArray {

clamp() {
// Clamp
this.words[this.sigBytes >>> 2] &=
this.words[this.sigBytes >>> 2]! &=
0xff_ff_ff_ff << (32 - (this.sigBytes % 4) * 8);
this.words.length = Math.ceil(this.sigBytes / 4);
}
@@ -56,7 +56,7 @@ export const Hex = {
// Convert
const hexChars: string[] = [];
for (let i = 0; i < wordArray.sigBytes; i++) {
const bite = (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
const bite = (wordArray.words[i >>> 2]! >>> (24 - (i % 4) * 8)) & 0xff;
hexChars.push((bite >>> 4).toString(16), (bite & 0x0f).toString(16));
}

@@ -70,11 +70,11 @@ export const Base64 = {
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const base64Chars: string[] = [];
for (let i = 0; i < wordArray.sigBytes; i += 3) {
const byte1 = (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
const byte1 = (wordArray.words[i >>> 2]! >>> (24 - (i % 4) * 8)) & 0xff;
const byte2 =
(wordArray.words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
(wordArray.words[(i + 1) >>> 2]! >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
const byte3 =
(wordArray.words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;
(wordArray.words[(i + 2) >>> 2]! >>> (24 - ((i + 2) % 4) * 8)) & 0xff;

const triplet = (byte1 << 16) | (byte2 << 8) | byte3;
for (let j = 0; j < 4 && i * 8 + j * 6 < wordArray.sigBytes * 8; j++) {
@@ -93,7 +93,7 @@ export const Latin1 = {
// Convert
const words: number[] = [];
for (let i = 0; i < latin1StrLength; i++) {
words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
words[i >>> 2]! |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
}

return new WordArray(words, latin1StrLength);
2 changes: 2 additions & 0 deletions src/crypto/js/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { murmurHash } from "./murmur";
export { sha256, sha256base64 } from "./sha256";
File renamed without changes.
2 changes: 1 addition & 1 deletion src/crypto/sha256.ts → src/crypto/js/sha256.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Based on https://github.com/brix/crypto-js 4.1.1 (MIT)

import { WordArray, Hasher, Base64 } from "./core";
import { WordArray, Hasher, Base64 } from "./_core";

// Initialization and round constants tables
const H = [
14 changes: 14 additions & 0 deletions src/crypto/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createHash } from "node:crypto";

export { murmurHash } from "../js/murmur";

export function sha256(data: string): string {
return createHash("sha256").update(data).digest("hex").replace(/=+$/, "");
}

export function sha256base64(date: string): string {
return createHash("sha256")
.update(date)
.digest("base64")
.replace(/[+/=]/g, "");
}
4 changes: 2 additions & 2 deletions src/hash/hash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { objectHash, HashOptions } from "./object-hash";
import { sha256base64 } from "../crypto/sha256";
import { objectHash, type HashOptions } from "./object-hash";
import { sha256base64 } from "ohash/crypto";

/**
* Hash any JS value into a string
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ export { objectHash } from "./hash/object-hash";
export { hash } from "./hash/hash";

// Crypto
export { murmurHash } from "./crypto/murmur";
export { sha256, sha256base64 } from "./crypto/sha256";
export { murmurHash } from "./crypto/js/murmur";
export { sha256, sha256base64 } from "./crypto/js/sha256";

// Utils
export { isEqual } from "./utils/is-equal";
2 changes: 1 addition & 1 deletion src/utils/diff.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { objectHash, HashOptions } from "../hash/object-hash";
import { objectHash, type HashOptions } from "../hash/object-hash";

/**
* Calculates the difference between two objects and returns a list of differences.
2 changes: 1 addition & 1 deletion src/utils/is-equal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { objectHash, HashOptions } from "../hash/object-hash";
import { objectHash, type HashOptions } from "../hash/object-hash";
/**
* Compare two objects using reference equality and stable deep hashing.
* @param {any} object1 First object
70 changes: 70 additions & 0 deletions test/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it } from "vitest";

import * as cryptoJS from "../src/crypto/js";
import * as cryptoNode from "../src/crypto/node";

const impls = {
js: cryptoJS,
node: cryptoNode,
};

describe("crypto", () => {
for (const [name, { sha256, sha256base64 }] of Object.entries(impls)) {
describe(name, () => {
it("sha256", () => {
expect(sha256("Hello World")).toBe(
"a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
);
expect(sha256("")).toBe(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
);
});

it("sha256base64", () => {
expect(sha256base64("Hello World")).toBe(
"pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4",
);
expect(sha256base64("")).toBe(
"47DEQpj8HBSaTImW5JCeuQeRkm5NMpJWZG3hSuFU",
);
});
});
}
});

describe("crypto:mmurmurHash", () => {
const { murmurHash } = cryptoJS;
it("Generates correct hash for 0 bytes without seed", () => {
expect(murmurHash("")).toMatchInlineSnapshot("0");
});
it("Generates correct hash for 0 bytes with seed", () => {
expect(murmurHash("", 1)).toMatchInlineSnapshot("1364076727"); // 0x514E28B7
});
it("Generates correct hash for 'Hello World'", () => {
expect(murmurHash("Hello World")).toMatchInlineSnapshot("427197390");
});
it("Generates the correct hash for varios string lengths", () => {
expect(murmurHash("a")).toMatchInlineSnapshot("1009084850");
expect(murmurHash("aa")).toMatchInlineSnapshot("923832745");
expect(murmurHash("aaa")).toMatchInlineSnapshot("3033554871");
expect(murmurHash("aaaa")).toMatchInlineSnapshot("2129582471");
expect(murmurHash("aaaaa")).toMatchInlineSnapshot("3922341931");
expect(murmurHash("aaaaaa")).toMatchInlineSnapshot("1736445713");
expect(murmurHash("aaaaaaa")).toMatchInlineSnapshot("1497565372");
expect(murmurHash("aaaaaaaa")).toMatchInlineSnapshot("3662943087");
expect(murmurHash("aaaaaaaaa")).toMatchInlineSnapshot("2724714153");
});
it("Works with Uint8Arrays", () => {
expect(
murmurHash(new Uint8Array([0x21, 0x43, 0x65, 0x87])),
).toMatchInlineSnapshot("4116402539"); // 0xF55B516B
});
it("Handles UTF-8 high characters correctly", () => {
expect(murmurHash("ππππππππ", 0x97_47_b2_8c)).toMatchInlineSnapshot(
"3581961153",
);
});
it("Gives correct hash with uint32 maximum value as seed", () => {
expect(murmurHash("a", 2_147_483_647)).toMatchInlineSnapshot("3574244913");
});
});
60 changes: 0 additions & 60 deletions test/index.test.ts

This file was deleted.

19 changes: 13 additions & 6 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"module": "preserve",
"moduleDetection": "force",
"esModuleInterop": true,
"types": ["node"],
"strict": true
},
"include": ["src"]
"allowSyntheticDefaultImports": true,
"allowJs": true,
"resolveJsonModule": true,
"strict": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"noImplicitOverride": true,
"noEmit": true
}
}