diff --git a/crypto/_benches/bench.ts b/crypto/_benches/bench.ts index 94f031d4eff0..b4901368ce66 100644 --- a/crypto/_benches/bench.ts +++ b/crypto/_benches/bench.ts @@ -1,9 +1,6 @@ #!/usr/bin/env -S deno bench // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - crypto as stdCrypto, - DIGEST_ALGORITHM_NAMES, -} from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; +import { crypto as stdCrypto, DIGEST_ALGORITHM_NAMES } from "../mod.ts"; import { crypto as oldCrypto } from "https://deno.land/std@0.220.1/crypto/mod.ts"; diff --git a/crypto/_fnv/fnv32.ts b/crypto/_fnv/fnv32.ts new file mode 100644 index 000000000000..d4904b78ac9d --- /dev/null +++ b/crypto/_fnv/fnv32.ts @@ -0,0 +1,32 @@ +// Ported from Go: +// https://github.com/golang/go/tree/go1.13.10/src/hash/fnv/fnv.go +// Copyright 2011 The Go Authors. All rights reserved. BSD license. +// https://github.com/golang/go/blob/master/LICENSE +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { mul32, swap32 } from "./util.ts"; + +const prime32 = 16777619; + +export const fnv32 = (data: Uint8Array): ArrayBuffer => { + let hash = 2166136261; + + data.forEach((c) => { + hash = mul32(hash, prime32); + hash ^= c; + }); + + return Uint32Array.from([swap32(hash)]).buffer; +}; + +export const fnv32a = (data: Uint8Array): ArrayBuffer => { + let hash = 2166136261; + + data.forEach((c) => { + hash ^= c; + hash = mul32(hash, prime32); + }); + + return Uint32Array.from([swap32(hash)]).buffer; +}; diff --git a/crypto/_fnv/fnv64.ts b/crypto/_fnv/fnv64.ts new file mode 100644 index 000000000000..24664b0754fe --- /dev/null +++ b/crypto/_fnv/fnv64.ts @@ -0,0 +1,35 @@ +// Ported from Go: +// https://github.com/golang/go/tree/go1.13.10/src/hash/fnv/fnv.go +// Copyright 2011 The Go Authors. All rights reserved. BSD license. +// https://github.com/golang/go/blob/master/LICENSE +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { mul64, swap32 } from "./util.ts"; + +const prime64Lo = 435; +const prime64Hi = 256; + +export const fnv64 = (data: Uint8Array): ArrayBuffer => { + let hashLo = 2216829733; + let hashHi = 3421674724; + + data.forEach((c) => { + [hashHi, hashLo] = mul64([hashHi, hashLo], [prime64Hi, prime64Lo]); + hashLo ^= c; + }); + + return new Uint32Array([swap32(hashHi >>> 0), swap32(hashLo >>> 0)]).buffer; +}; + +export const fnv64a = (data: Uint8Array): ArrayBuffer => { + let hashLo = 2216829733; + let hashHi = 3421674724; + + data.forEach((c) => { + hashLo ^= c; + [hashHi, hashLo] = mul64([hashHi, hashLo], [prime64Hi, prime64Lo]); + }); + + return new Uint32Array([swap32(hashHi >>> 0), swap32(hashLo >>> 0)]).buffer; +}; diff --git a/crypto/_fnv/mod.ts b/crypto/_fnv/mod.ts new file mode 100644 index 000000000000..ee7278b3a26f --- /dev/null +++ b/crypto/_fnv/mod.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { fnv32, fnv32a } from "./fnv32.ts"; +import { fnv64, fnv64a } from "./fnv64.ts"; + +export function fnv(name: string, buf?: Uint8Array): ArrayBuffer { + if (!buf) { + throw new TypeError("no data provided for hashing"); + } + + switch (name) { + case "FNV32": + return fnv32(buf); + case "FNV64": + return fnv64(buf); + case "FNV32A": + return fnv32a(buf); + case "FNV64A": + return fnv64a(buf); + default: + throw new TypeError(`unsupported fnv digest: ${name}`); + } +} diff --git a/crypto/_fnv/util.ts b/crypto/_fnv/util.ts new file mode 100644 index 000000000000..15a2fefd3ba5 --- /dev/null +++ b/crypto/_fnv/util.ts @@ -0,0 +1,62 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +export function swap32(val: number): number { + return ( + ((val & 0xff) << 24) | + ((val & 0xff00) << 8) | + ((val >> 8) & 0xff00) | + ((val >> 24) & 0xff) + ); +} + +function n16(n: number): number { + return n & 0xffff; +} + +function n32(n: number): number { + return n >>> 0; +} + +function add32WithCarry(a: number, b: number): [number, number] { + const added = n32(a) + n32(b); + return [n32(added), added > 0xffffffff ? 1 : 0]; +} + +function mul32WithCarry(a: number, b: number): [number, number] { + const al = n16(a); + const ah = n16(a >>> 16); + const bl = n16(b); + const bh = n16(b >>> 16); + + const [t, tc] = add32WithCarry(al * bh, ah * bl); + const [n, nc] = add32WithCarry(al * bl, n32(t << 16)); + const carry = nc + (tc << 16) + n16(t >>> 16) + ah * bh; + + return [n, carry]; +} + +/** + * mul32 performs 32-bit multiplication, a * b + * @param a + * @param b + */ +export function mul32(a: number, b: number): number { + // https://stackoverflow.com/a/28151933 + const al = n16(a); + const ah = a - al; + return n32(n32(ah * b) + al * b); +} + +/** + * mul64 performs 64-bit multiplication with two 32-bit words + * @param [ah, al] + * @param [bh, bl] + */ +export function mul64( + [ah, al]: [number, number], + [bh, bl]: [number, number], +): [number, number] { + const [n, c] = mul32WithCarry(al, bl); + return [n32(mul32(al, bh) + mul32(ah, bl) + c), n]; +} diff --git a/crypto/_fnv/util_test.ts b/crypto/_fnv/util_test.ts new file mode 100644 index 000000000000..49f3ed0e9b91 --- /dev/null +++ b/crypto/_fnv/util_test.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../../assert/mod.ts"; +import { mul32, mul64 } from "./util.ts"; + +Deno.test("mul32()", () => { + assertEquals(mul32(0xffffffff, 0xffffffff), 1); + assertEquals(mul32(0x12345678, 0xdeadbeef), 0x5621ca08); + assertEquals(mul32(0xf626f430, 0xff7469f1), 0x2a939130); + assertEquals(mul32(0x543f9412, 0x8a4aa84f), 0x39fe818e); + assertEquals(mul32(0x8ee170d1, 0x2fbbb9ec), 0x6a0609ac); + assertEquals(mul32(0xea3b3a14, 0xa397bd0a), 0xddfd08c8); + assertEquals(mul32(0x93f8536b, 0xa79e3c04), 0xcc7861ac); + assertEquals(mul32(0xf97dab98, 0xed526241), 0x2348c198); + assertEquals(mul32(0x35500191, 0xd5012447), 0xaff9d337); + assertEquals(mul32(0x471dde47, 0xaaa4950c), 0x4341be54); + assertEquals(mul32(0xd633970d, 0xa9bc2bcd), 0xb43b2469); + assertEquals(mul32(0xc60898cc, 0xbfe7dcc4), 0x15f84c30); +}); + +Deno.test("mul64()", () => { + assertEquals(mul64([0xffffffff, 0xffffffff], [0xffffffff, 0xffffffff]), [ + 0, + 1, + ]); + assertEquals(mul64([0x12345678, 0xdeadbeef], [0xcafebabe, 0xbaadf00d]), [ + 0xc801c86b, + 0xdf55c223, + ]); + assertEquals(mul64([0xdc479aed, 0x24bc71a3], [0x543717c1, 0x4b6056b9]), [ + 0x56c7ec8f, + 0x387ae0cb, + ]); + assertEquals(mul64([0xb84936ae, 0xb84becd2], [0x2864edd1, 0x14ee13cc]), [ + 0xd87e9171, + 0x12504d58, + ]); + assertEquals(mul64([0xb0b73e95, 0x3f5cc701], [0x6c7b30b8, 0xcd7f0f9e]), [ + 0x570551ee, + 0x116ae19e, + ]); + assertEquals(mul64([0xc237b433, 0x160b50bf], [0x3f937c23, 0xf26175f7]), [ + 0x48a1d118, + 0x97313349, + ]); + assertEquals(mul64([0x386242fd, 0x6baa0fc0], [0xf81f7e23, 0xbe172381]), [ + 0x4799f2a3, + 0x6b192fc0, + ]); + assertEquals(mul64([0x5afc8714, 0x902180d1], [0xa7068c96, 0xb859bb4d]), [ + 0xb4589d29, + 0xd3d569dd, + ]); + assertEquals(mul64([0xb4e86a68, 0x619bee92], [0xd67560fa, 0x736982a7]), [ + 0x72c73b5d, + 0x4bc0c53e, + ]); + assertEquals(mul64([0xfc8b5561, 0xbf91d6d5], [0x2bcb029a, 0xa144ead3]), [ + 0x2da439a7, + 0x3926c38f, + ]); + assertEquals(mul64([0x47b62fae, 0xffe8cb4c], [0xbda77111, 0x6cad4968]), [ + 0x9d9b7832, + 0xcae742e0, + ]); + assertEquals(mul64([0xc9160fc1, 0xd96e085b], [0x3adfd031, 0x3f75e557]), [ + 0xe4d0bf23, + 0x88753ded, + ]); +});