diff --git a/README.md b/README.md
index 53ff93da..bd4805bd 100644
--- a/README.md
+++ b/README.md
@@ -486,6 +486,21 @@ Uses double SHA-256 for checksum calculation
Create a base58check encoder/decoder with custom hash functions
+### @exodus/bytes/crc.js 
+
+Implements crc32 from [IEEE 802.3](https://standards.ieee.org/ieee/802.3/10422/),
+[ISO 3309](https://www.iso.org/standard/8561.html),
+[ISO/IEC 13239:2002](https://www.iso.org/standard/37010.html),
+and others.
+
+```js
+import { crc32 } from '@exodus/bytes/crc.js'
+```
+
+#### `crc32(arr)`
+
+Calculate the CRC-32 of a `Uint8Array`, as an unsigned number.
+
### @exodus/bytes/wif.js 
Wallet Import Format (WIF) encoding and decoding.
diff --git a/benchmarks/crc.bench.js b/benchmarks/crc.bench.js
new file mode 100644
index 00000000..518dd193
--- /dev/null
+++ b/benchmarks/crc.bench.js
@@ -0,0 +1,60 @@
+import * as exodus from '@exodus/bytes/crc.js'
+import { benchmark } from '@exodus/test/benchmark' // eslint-disable-line @exodus/import/no-unresolved
+import crc from 'crc'
+import crc32 from 'crc-32'
+import { describe, test } from 'node:test'
+import * as zlib from 'node:zlib'
+
+import { Table } from './utils/table.js'
+
+const columns = ['@exodus/bytes/crc32', 'crc', 'crc-32', 'zlib']
+
+const seed = crypto.getRandomValues(new Uint8Array(5 * 1024))
+
+const bufs32 = []
+const bufs5mb = []
+const N = 3000
+
+for (let i = 0; i < N; i++) {
+ bufs5mb.push(seed.map((x, j) => x + i * j))
+ const at = Math.floor(Math.random() * 100)
+ bufs32.push(seed.subarray(at, at + 32).map((x, j) => x + i * j))
+}
+
+describe('benchmarks: crc32', async () => {
+ // [name, impl, skip]
+ const libs = [
+ ['@exodus/bytes/crc32', (x) => exodus.crc32(x)],
+ ['crc', (x) => crc.crc32(x)],
+ ['crc-32', (x) => crc32.buf(x)],
+ ['zlib', (x) => zlib.crc32(x), !zlib.crc32],
+ ]
+
+ test('crc32 coherence', (t) => {
+ for (let i = 0; i < 10; i++) {
+ for (const [name, f, skip] of libs) {
+ if (skip) continue
+ t.assert.deepEqual(f(bufs32[i]) >>> 0, exodus.crc32(bufs32[i]), name)
+ t.assert.deepEqual(f(bufs5mb[i]) >>> 0, exodus.crc32(bufs5mb[i]), name)
+ }
+ }
+ })
+
+ test('crc32, 32 bytes', { timeout: 10_000 }, async () => {
+ const res = new Table()
+ for (const [name, f, skip] of libs) {
+ res.add(name, await benchmark(`crc32: ${name}`, { skip, args: bufs32 }, f))
+ }
+
+ res.print(columns)
+ })
+
+ test('crc32, 5 KiB', { timeout: 10_000 }, async () => {
+ const res = new Table()
+ for (const [name, f, skip] of libs) {
+ res.add(name, await benchmark(`crc32: ${name}`, { skip, args: bufs5mb }, f))
+ }
+
+ res.print(columns)
+ })
+})
diff --git a/crc.d.ts b/crc.d.ts
new file mode 100644
index 00000000..a2c45cea
--- /dev/null
+++ b/crc.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Implements crc32 from [IEEE 802.3](https://standards.ieee.org/ieee/802.3/10422/),
+ * [ISO 3309](https://www.iso.org/standard/8561.html),
+ * [ISO/IEC 13239:2002](https://www.iso.org/standard/37010.html),
+ * and others.
+ *
+ * ```js
+ * import { crc32 } from '@exodus/bytes/crc.js'
+ * ```
+ *
+ * @module @exodus/bytes/crc.js
+ */
+
+/**
+ * Calculate the CRC-32 of a `Uint8Array`, as an unsigned number.
+ *
+ * @param arr - The input bytes
+ * @returns CRC-32 as an unsigned number from 0 to 2**32-1
+ */
+export function crc32(arr: Uint8Array): number;
diff --git a/crc.js b/crc.js
new file mode 100644
index 00000000..d74b49e4
--- /dev/null
+++ b/crc.js
@@ -0,0 +1,52 @@
+import { assertU8 } from './fallback/_utils.js'
+import { crc32Table } from './fallback/crc.js'
+import { isHermes, isLE } from './fallback/platform.js'
+
+const T = crc32Table()
+
+const [T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, Ta, Tb, Tc, Td, Te, Tf] = Array.from(
+ { length: 16 },
+ (_, k) => T.subarray(k * 256, k * 256 + 256)
+)
+
+export function crc32(x) {
+ assertU8(x)
+ let c = -1
+ let i = 0
+ const n = x.length
+
+ if (isLE && n > 1024) {
+ const pre = (4 - (x.byteOffset & 3)) & 3
+ for (; i < pre; i++) c = T0[(c ^ x[i]) & 0xff] ^ (c >>> 8)
+ const words = (n - i) >>> 2
+ const W = new (isHermes ? Uint32Array : Int32Array)(x.buffer, x.byteOffset + i, words)
+ let j = 0
+ for (const end = words - 3; j < end; j += 4) {
+ const a = W[j] ^ c
+ const b = W[j + 1]
+ const d = W[j + 2]
+ const e = W[j + 3]
+ // prettier-ignore
+ c =
+ T[0xf_00 + (a & 0xff)] ^ T[0xe_00 + ((a >>> 8) & 0xff)] ^ T[0xd_00 + ((a >>> 16) & 0xff)] ^ T[0xc_00 + (a >>> 24)] ^
+ T[0xb_00 + (b & 0xff)] ^ T[0xa_00 + ((b >>> 8) & 0xff)] ^ T[0x9_00 + ((b >>> 16) & 0xff)] ^ T[0x8_00 + (b >>> 24)] ^
+ T[0x7_00 + (d & 0xff)] ^ T[0x6_00 + ((d >>> 8) & 0xff)] ^ T[0x5_00 + ((d >>> 16) & 0xff)] ^ T[0x4_00 + (d >>> 24)] ^
+ T[0x3_00 + (e & 0xff)] ^ T[0x2_00 + ((e >>> 8) & 0xff)] ^ T[0x1_00 + ((e >>> 16) & 0xff)] ^ T[e >>> 24]
+ }
+
+ i += j * 4 // not << 2, so that inputs >= 2 GiB stay correct
+ } else {
+ for (const end = n - 15; i < end; i += 16) {
+ // prettier-ignore
+ c =
+ Tf[(x[i] ^ c) & 0xff] ^ Te[(x[i + 1] ^ (c >>> 8)) & 0xff] ^
+ Td[(x[i + 2] ^ (c >>> 16)) & 0xff] ^ Tc[x[i + 3] ^ (c >>> 24)] ^
+ Tb[x[i + 4]] ^ Ta[x[i + 5]] ^ T9[x[i + 6]] ^ T8[x[i + 7]] ^
+ T7[x[i + 8]] ^ T6[x[i + 9]] ^ T5[x[i + 10]] ^ T4[x[i + 11]] ^
+ T3[x[i + 12]] ^ T2[x[i + 13]] ^ T1[x[i + 14]] ^ T0[x[i + 15]]
+ }
+ }
+
+ for (let k = i; k < n; k++) c = T0[(c ^ x[k]) & 0xff] ^ (c >>> 8)
+ return ~c >>> 0
+}
diff --git a/crc.node.js b/crc.node.js
new file mode 100644
index 00000000..197d531d
--- /dev/null
+++ b/crc.node.js
@@ -0,0 +1,38 @@
+import { crc32 as native } from 'node:zlib'
+import { assertU8 } from './fallback/_utils.js'
+import { crc32Table } from './fallback/crc.js'
+
+const T = crc32Table()
+const [T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, Ta, Tb, Tc, Td, Te, Tf] = Array.from(
+ { length: 16 },
+ (_, k) => T.subarray(k * 256, k * 256 + 256)
+)
+
+// zlib.crc32 is a V8 fast API call since Node.js 24.9.0 (nodejs/node#59813)
+const [major, minor] = (process.versions?.node || '0').split('.').map(Number)
+const fastNative = major > 24 || (major === 24 && minor >= 9)
+
+// Node.js x64 builds have SIMD crc32 disabled in zlib (nodejs/node#45268)
+// arm64 builds use the hardware crc32x instruction
+const NATIVE_MIN =
+ !!globalThis.Deno || !!globalThis.Bun ? 0 : process.arch === 'arm64' ? (fastNative ? 0 : 64) : 256
+
+export function crc32(x) {
+ assertU8(x)
+ const n = x.length
+ if (n >= NATIVE_MIN) return native(x)
+ let c = -1
+ let i = 0
+ for (const end = n - 15; i < end; i += 16) {
+ // prettier-ignore
+ c =
+ Tf[(x[i] ^ c) & 0xff] ^ Te[(x[i + 1] ^ (c >>> 8)) & 0xff] ^
+ Td[(x[i + 2] ^ (c >>> 16)) & 0xff] ^ Tc[x[i + 3] ^ (c >>> 24)] ^
+ Tb[x[i + 4]] ^ Ta[x[i + 5]] ^ T9[x[i + 6]] ^ T8[x[i + 7]] ^
+ T7[x[i + 8]] ^ T6[x[i + 9]] ^ T5[x[i + 10]] ^ T4[x[i + 11]] ^
+ T3[x[i + 12]] ^ T2[x[i + 13]] ^ T1[x[i + 14]] ^ T0[x[i + 15]]
+ }
+
+ for (let j = i; j < n; j++) c = T0[(c ^ x[j]) & 0xff] ^ (c >>> 8)
+ return ~c >>> 0
+}
diff --git a/fallback/crc.js b/fallback/crc.js
new file mode 100644
index 00000000..1a04b60c
--- /dev/null
+++ b/fallback/crc.js
@@ -0,0 +1,18 @@
+import { isHermes } from './platform.js'
+
+export function crc32Table() {
+ const T = new (isHermes ? Uint32Array : Int32Array)(16 * 256) // Signed to fit int32 on read
+
+ for (let n = 0; n < 256; n++) {
+ let c = n
+ for (let i = 0; i < 8; i++) c = c & 1 ? 0xed_b8_83_20 ^ (c >>> 1) : c >>> 1
+ T[n] = c
+ }
+
+ for (let n = 0; n < 256; n++) {
+ let v = T[n]
+ for (let c = 256 + n; c < T.length; c += 256) v = T[c] = (v >>> 8) ^ T[v & 0xff]
+ }
+
+ return T
+}
diff --git a/package.json b/package.json
index 380b34a6..007a7fb0 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
"/fallback/base32.js",
"/fallback/base58check.js",
"/fallback/base64.js",
+ "/fallback/crc.js",
"/fallback/encoding.js",
"/fallback/encoding.api.js",
"/fallback/encoding.labels.js",
@@ -106,6 +107,9 @@
"/bech32.d.ts",
"/bigint.js",
"/bigint.d.ts",
+ "/crc.js",
+ "/crc.d.ts",
+ "/crc.node.js",
"/encoding-browser.js",
"/encoding-browser.browser.js",
"/encoding-browser.native.js",
@@ -175,6 +179,11 @@
"types": "./bigint.d.ts",
"default": "./bigint.js"
},
+ "./crc.js": {
+ "types": "./crc.d.ts",
+ "node": "./crc.node.js",
+ "default": "./crc.js"
+ },
"./hex.js": {
"types": "./hex.d.ts",
"node": "./hex.node.js",
@@ -272,6 +281,8 @@
"bstring": "^0.3.9",
"buffer": "^6.0.3",
"c8": "^10.1.3",
+ "crc": "^4.3.2",
+ "crc-32": "^1.2.2",
"decode-utf8": "^1.0.1",
"electron": "39.4.0",
"encode-utf8": "^2.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5e3be4c4..50b91e2f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -83,6 +83,12 @@ importers:
c8:
specifier: ^10.1.3
version: 10.1.3
+ crc:
+ specifier: ^4.3.2
+ version: 4.3.2(buffer@6.0.3)
+ crc-32:
+ specifier: ^1.2.2
+ version: 1.2.2
decode-utf8:
specifier: ^1.0.1
version: 1.0.1
@@ -1183,6 +1189,20 @@ packages:
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz}
+ crc-32@1.2.2:
+ resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==, tarball: https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz}
+ engines: {node: '>=0.8'}
+ hasBin: true
+
+ crc@4.3.2:
+ resolution: {integrity: sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==, tarball: https://registry.npmjs.org/crc/-/crc-4.3.2.tgz}
+ engines: {node: '>=12'}
+ peerDependencies:
+ buffer: '>=6.0.3'
+ peerDependenciesMeta:
+ buffer:
+ optional: true
+
cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==, tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz}
@@ -4426,6 +4446,12 @@ snapshots:
core-util-is@1.0.3: {}
+ crc-32@1.2.2: {}
+
+ crc@4.3.2(buffer@6.0.3):
+ optionalDependencies:
+ buffer: 6.0.3
+
cross-spawn@5.1.0:
dependencies:
lru-cache: 4.1.5
diff --git a/tests/crc.test.js b/tests/crc.test.js
new file mode 100644
index 00000000..0e3599c0
--- /dev/null
+++ b/tests/crc.test.js
@@ -0,0 +1,263 @@
+import { crc32 } from '@exodus/bytes/crc.js'
+import * as lib from '../crc.js'
+import { randomValues } from '@exodus/crypto/randomBytes'
+import { describe, test } from 'node:test'
+
+const SharedArrayBuffer = globalThis.SharedArrayBuffer ?? ArrayBuffer
+const toShared = (u8, offset = 0) => {
+ const res = new Uint8Array(new SharedArrayBuffer(u8.length + offset)).subarray(offset)
+ res.set(u8)
+ return res
+}
+
+// Bitwise reference implementation (no tables, no fast paths), for coherence checks
+function crc32reference(arr) {
+ let c = 0xff_ff_ff_ff
+ for (let i = 0; i < arr.length; i++) {
+ c ^= arr[i]
+ for (let k = 0; k < 8; k++) c = c & 1 ? (c >>> 1) ^ 0xed_b8_83_20 : c >>> 1
+ }
+
+ return (c ^ 0xff_ff_ff_ff) >>> 0
+}
+
+const ascii = (str) => Uint8Array.from(str, (c) => c.charCodeAt(0))
+
+// Well-known CRC-32 test vectors, e.g. Go hash/crc32 test suite
+// prettier-ignore
+const STRINGS = [
+ ['', 0x00_00_00_00],
+ ['a', 0xe8_b7_be_43],
+ ['ab', 0x9e_83_48_6d],
+ ['abc', 0x35_24_41_c2],
+ ['abcd', 0xed_82_cd_11],
+ ['abcde', 0x85_87_d8_65],
+ ['abcdef', 0x4b_8e_39_ef],
+ ['abcdefg', 0x31_2a_6a_a6],
+ ['abcdefgh', 0xae_ef_2a_50],
+ ['abcdefghi', 0x8d_a9_88_af],
+ ['abcdefghij', 0x39_81_70_3a],
+ ['123456789', 0xcb_f4_39_26], // "check" value from the CRC catalogue
+ ['The quick brown fox jumps over the lazy dog', 0x41_4f_a3_39],
+ ['Discard medicine more than two years old.', 0x6b_9c_df_e7],
+ ['He who has a shady past knows that nice guys finish last.', 0xc9_0e_f7_3f],
+ ["I wouldn't marry him with a ten foot pole.", 0xb9_02_34_1f],
+ ['Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave', 0x04_20_80_e8],
+ ['The days of the digital watch are numbered. -Tom Stoppard', 0x15_4c_6d_11],
+ ["Nepal premier won't resign.", 0x4c_41_83_25],
+ ['For every action there is an equal and opposite government program.', 0x33_95_51_50],
+ ["His money is twice tainted: 'taint yours and 'taint mine.", 0x26_21_6a_4b],
+ ['There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977', 0x1a_bb_e4_5e],
+ ["It's a tiny change to the code and not completely disgusting. - Bob Manchek", 0xc8_9a_94_f7],
+ ['size: a.out: bad magic', 0xab_3a_be_14],
+ ['The major problem is with sendmail. -Mark Horton', 0xba_b1_02_b6],
+ ['Give me a rock, paper and scissors and I will move the world. CCFestoon', 0x99_91_49_d7],
+ ['If the enemy is within range, then so are you.', 0x6d_52_a3_3c],
+ ["It's well we cannot hear the screams/That we create in others' dreams.", 0x90_63_1e_8d],
+ ["You remind me of a TV show, but that's all right: I watch it anyway.", 0x78_30_91_30],
+ ['C is as portable as Stonehedge!!', 0x7d_0a_37_7f],
+ ['Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley', 0x8c_79_fd_79],
+ ['The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule', 0xa2_0b_71_67],
+ ['How can you write a big system without C++? -Paul Glick', 0x8e_0b_b4_43],
+]
+
+const zeros = (n) => new Uint8Array(n)
+const filled = (n, v) => new Uint8Array(n).fill(v)
+const incrementing = (n) => Uint8Array.from({ length: n }, (_, i) => i & 0xff)
+const decrementing = (n) => Uint8Array.from({ length: n }, (_, i) => (n - 1 - i) & 0xff)
+
+// prettier-ignore
+const BYTES = [
+ ['[0] x32', zeros(32), 0x19_0a_55_ad],
+ ['[255] x32', filled(32, 0xff), 0xff_6c_ab_0b],
+ ['0..31', incrementing(32), 0x91_26_7e_8a],
+ ['31..0', decrementing(32), 0x9a_b0_ef_72],
+ ['0..63', incrementing(64), 0x10_0e_ce_8c],
+ ['[0] x65', zeros(65), 0x1d_cd_f7_77],
+ ['0..64', incrementing(65), 0x40_c0_6f_d8],
+ ['0..255', incrementing(256), 0x29_05_8c_73],
+ ['[0] x512', zeros(512), 0xb2_aa_75_78],
+ ['[0] x1024', zeros(1024), 0xef_b5_af_2e],
+ ['[255] x1024', filled(1024, 0xff), 0xb8_3a_ff_f4],
+ ['(0..255) x4', incrementing(1024), 0xb7_0b_4c_26],
+ ['(0..255) x16', incrementing(4096), 0xa2_91_20_82],
+]
+
+const INVALID = [
+ null,
+ undefined,
+ [],
+ [1, 2],
+ 'string',
+ '',
+ 0,
+ 12,
+ {},
+ new Uint16Array(1),
+ new Uint8ClampedArray(1),
+ new Int8Array(1),
+ new ArrayBuffer(4),
+ new DataView(new ArrayBuffer(4)),
+]
+
+const seed = randomValues(2048) // enough for the largest size + offset below
+
+const skipLarge =
+ process.env.EXODUS_TEST_PLATFORM === 'quickjs' ||
+ process.env.EXODUS_TEST_PLATFORM === 'xs' ||
+ process.env.EXODUS_TEST_PLATFORM === 'boa' ||
+ process.env.EXODUS_TEST_PLATFORM === 'graaljs' ||
+ process.env.EXODUS_TEST_PLATFORM === 'engine262'
+
+describe('crc32', () => {
+ test('invalid input', (t) => {
+ for (const input of INVALID) {
+ t.assert.throws(() => crc32(input), TypeError)
+ t.assert.throws(() => lib.crc32(input), TypeError)
+ }
+ })
+
+ test('fixtures, strings', (t) => {
+ for (const [str, expected] of STRINGS) {
+ const uint8 = ascii(str)
+ t.assert.strictEqual(crc32reference(uint8), expected, `reference: ${str}`)
+ for (const arg of [uint8, toShared(uint8), Buffer.from(uint8)]) {
+ t.assert.strictEqual(crc32(arg), expected, str)
+ t.assert.strictEqual(lib.crc32(arg), expected, str)
+ }
+ }
+ })
+
+ test('fixtures, bytes', (t) => {
+ for (const [name, uint8, expected] of BYTES) {
+ t.assert.strictEqual(crc32reference(uint8), expected, `reference: ${name}`)
+ for (const arg of [uint8, toShared(uint8), Buffer.from(uint8)]) {
+ t.assert.strictEqual(crc32(arg), expected, name)
+ t.assert.strictEqual(lib.crc32(arg), expected, name)
+ }
+ }
+ })
+
+ test('returns an unsigned 32-bit integer', (t) => {
+ for (const uint8 of [ascii('a'), ascii('abc'), seed, ...BYTES.map(([, x]) => x)]) {
+ for (const res of [crc32(uint8), lib.crc32(uint8)]) {
+ t.assert.strictEqual(typeof res, 'number')
+ t.assert.ok(Number.isInteger(res))
+ t.assert.ok(res >= 0 && res <= 0xff_ff_ff_ff)
+ t.assert.strictEqual(res >>> 0, res)
+ }
+ }
+
+ // Values with the top bit set stay positive
+ t.assert.strictEqual(crc32(ascii('a')), 0xe8_b7_be_43)
+ t.assert.ok(crc32(ascii('a')) > 0x7f_ff_ff_ff)
+ })
+
+ test('does not depend on the buffer around the view', (t) => {
+ for (const size of [
+ 0, 1, 3, 4, 5, 15, 16, 17, 63, 64, 65, 100, 255, 256, 257, 511, 512, 513, 1024, 1025,
+ ]) {
+ for (const offset of [0, 1, 2, 3, 4, 5, 6, 7, 64, 65, 100, 101]) {
+ if (offset + size > seed.length) continue
+ const arr = seed.subarray(offset, offset + size)
+ const copy = Uint8Array.from(arr)
+ t.assert.strictEqual(copy.byteOffset, 0)
+ const expected = crc32reference(copy)
+ t.assert.strictEqual(crc32(copy), expected, `x${size} copy`)
+ t.assert.strictEqual(crc32(arr), expected, `x${size} +${offset}`)
+ t.assert.strictEqual(lib.crc32(arr), expected, `x${size} +${offset}`)
+ t.assert.strictEqual(crc32(toShared(arr, offset)), expected, `x${size} +${offset} shared`)
+ t.assert.strictEqual(
+ lib.crc32(toShared(arr, offset)),
+ expected,
+ `x${size} +${offset} shared`
+ )
+ const buffer = Buffer.from(arr) // pooled on small sizes, so has arbitrary byteOffset
+ t.assert.strictEqual(crc32(buffer), expected, `x${size} +${offset} Buffer`)
+ t.assert.strictEqual(lib.crc32(buffer), expected, `x${size} +${offset} Buffer`)
+ }
+ }
+ })
+
+ test('sizes and offsets, random data', (t) => {
+ // All small sizes at all alignments: covers the byte path with all tail lengths, and crosses
+ // both native thresholds of the Node.js entry (64 on arm64 before 24.9, 256 elsewhere)
+ for (let offset = 0; offset < 8; offset++) {
+ for (let size = 0; size <= 300; size++) {
+ const arr = seed.subarray(offset, offset + size)
+ const expected = crc32reference(arr)
+ t.assert.strictEqual(crc32(arr), expected, `random x${size} +${offset}`)
+ t.assert.strictEqual(lib.crc32(arr), expected, `random x${size} +${offset}`)
+ t.assert.strictEqual(
+ crc32(toShared(arr, offset)),
+ expected,
+ `random x${size} +${offset} shared`
+ )
+ t.assert.strictEqual(
+ lib.crc32(toShared(arr, offset)),
+ expected,
+ `random x${size} +${offset} shared`
+ )
+ t.assert.strictEqual(crc32(Buffer.from(arr)), expected, `random x${size} +${offset} Buffer`)
+ }
+ }
+ })
+
+ test('sizes, random data, around the word-sliced threshold', (t) => {
+ // crc.js switches to 32-bit reads above 1024 bytes: cross it at all alignment prefixes and tail lengths
+ for (let size = 960; size <= 1100; size++) {
+ const offset = size & 7
+ const arr = seed.subarray(offset, offset + size)
+ t.assert.strictEqual(arr.length, size)
+ const expected = lib.crc32(arr)
+ t.assert.strictEqual(crc32(arr), expected, `random x${size} +${offset}`)
+ t.assert.strictEqual(
+ crc32(toShared(arr, offset)),
+ expected,
+ `random x${size} +${offset} shared`
+ )
+ t.assert.strictEqual(
+ lib.crc32(toShared(arr, offset)),
+ expected,
+ `random x${size} +${offset} shared`
+ )
+ t.assert.strictEqual(crc32(Buffer.from(arr)), expected, `random x${size} +${offset} Buffer`)
+ if (size % 64 === 0)
+ t.assert.strictEqual(crc32reference(arr), expected, `random x${size} reference`)
+ }
+ })
+
+ test('large input', { skip: skipLarge }, (t) => {
+ const block = new Uint8Array(64 * seed.length)
+ for (let k = 0; k < 64; k++)
+ block.set(
+ seed.map((x, j) => x + k * j),
+ k * seed.length
+ )
+ const large = new Uint8Array(64 * block.length + 7)
+ for (let i = 0; i < large.length; i += block.length) {
+ large.set(block.subarray(0, large.length - i), i)
+ }
+
+ for (const offset of [0, 1, 2, 3, 4, 7]) {
+ for (const size of [block.length, large.length - 7]) {
+ const arr = large.subarray(offset, offset + size)
+ const expected = lib.crc32(arr)
+ t.assert.strictEqual(crc32(arr), expected, `large x${size} +${offset}`)
+ t.assert.strictEqual(
+ crc32(toShared(arr, offset)),
+ expected,
+ `large x${size} +${offset} shared`
+ )
+ t.assert.strictEqual(
+ lib.crc32(toShared(arr, offset)),
+ expected,
+ `large x${size} +${offset} shared`
+ )
+ }
+ }
+
+ // The reference is affordable once on a 64 KiB block
+ t.assert.strictEqual(crc32reference(block), lib.crc32(block), 'reference x65536')
+ })
+})