Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,21 @@ Uses double SHA-256 for checksum calculation

Create a base58check encoder/decoder with custom hash functions

### @exodus/bytes/crc.js <sub>![](https://img.shields.io/bundlejs/size/@exodus/bytes/crc.js?style=flat-square)</sub>

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 <sub>![](https://img.shields.io/bundlejs/size/@exodus/bytes/wif.js?style=flat-square)</sub>

Wallet Import Format (WIF) encoding and decoding.
Expand Down
60 changes: 60 additions & 0 deletions benchmarks/crc.bench.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
20 changes: 20 additions & 0 deletions crc.d.ts
Original file line number Diff line number Diff line change
@@ -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;
52 changes: 52 additions & 0 deletions crc.js
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions crc.node.js
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions fallback/crc.js
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading