
Security News
arXiv Is Rate Limiting Authors Following a Flood of AI Slop Submissions
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.
@exodus/bytes
Advanced tools
@exodus/bytesUint8Array conversion to and from base64, base32, base58, hex, utf8, utf16, bech32 and wif
And a TextEncoder / TextDecoder polyfill
Performs proper input validation, ensures no garbage-in-garbage-out
Tested on Node.js, Deno, Bun, browsers (including Servo), Hermes, QuickJS and barebone engines in CI (how?)
10-20x faster than Buffer polyfill2-10x faster than iconv-liteThe above was for the js fallback
It's up to 100x when native impl is available
e.g. in utf8fromString on Hermes / React Native or fromHex in Chrome
Also:
3-8x faster than bs5810-30x faster than @scure/base (or >100x on Node.js <25)utf8toString / utf8fromString than Buffer or TextDecoder / TextEncoder on Node.jsSee Performance for more info
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding.js'
Less than half the bundle size of text-encoding, whatwg-encoding or iconv-lite (gzipped or not), and is much faster. See also lite version.
Spec compliant, passing WPT and covered with extra tests.
Moreover, tests for this library uncovered bugs in all major implementations.
Faster than Node.js native implementation on Node.js. Runs (and passes WPT) on Node.js built without ICU.
TextDecoder / TextEncoder APIs are lossy by default per specThese are only provided as a compatibility layer, prefer hardened APIs instead in new code.
TextDecoder can (and should) be used with { fatal: true } option for all purposes demanding correctness / lossless transforms
TextEncoder does not support a fatal mode per spec, it always performs replacement.
That is not suitable for hashing, cryptography or consensus applications.
Otherwise there would be non-equal strings with equal signatures and hashes — the collision is caused by the lossy transform of a JS string to bytes.
Those also survive e.g. JSON.stringify/JSON.parse or being sent over network.
Use strict APIs in new applications, see utf8fromString / utf16fromString below.
Those throw on non-well-formed strings by default.
If you don't need support for legacy multi-byte encodings, you can use the lite import:
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding-lite.js'
This reduces the bundle size 10x:
from 90 KiB gzipped for @exodus/bytes/encoding.js to 9 KiB gzipped for @exodus/bytes/encoding-lite.js.
(For comparison, text-encoding module is 190 KiB gzipped, and iconv-lite is 194 KiB gzipped).
It still supports utf-8, utf-16le, utf-16be and all single-byte encodings specified by the spec,
the only difference is support for legacy multi-byte encodings.
@exodus/bytes/utf8.jsutf8fromString(str, format = 'uint8')utf8fromStringLoose(str, format = 'uint8')utf8toString(arr)utf8toStringLoose(arr)@exodus/bytes/utf16.jsutf16fromString(str, format = 'uint16')utf16fromStringLoose(str, format = 'uint16')utf16toString(arr, 'uint16')utf16toStringLoose(arr, 'uint16')@exodus/bytes/single-byte.jscreateSinglebyteDecoder(encoding, loose = false)Create a decoder for a supported one-byte encoding.
Returns a function decode(arr) that decodes bytes to a string.
@exodus/bytes/multi-byte.jscreateMultibyteDecoder(encoding, loose = false)Create a decoder for a supported legacy multi-byte encoding.
Returns a function decode(arr, stream = false) that decodes bytes to a string.
That function will have state while stream = true is used.
windows1252toString(arr)Decode windows-1252 bytes to a string.
Also supports ascii and latin-1 as those are strict subsets of windows-1252.
There is no loose variant for this encoding, all bytes can be decoded.
Same as windows1252toString = createSinglebyteDecoder('windows-1252').
@exodus/bytes/bigint.jsfromBigInt(bigint, { length, format = 'uint8' })toBigInt(arr)@exodus/bytes/hex.jstoHex(arr)fromHex(string)@exodus/bytes/base64.jstoBase64(arr, { padding = true })toBase64url(arr, { padding = false })fromBase64(str, { format = 'uint8', padding = 'both' })fromBase64url(str, { format = 'uint8', padding = false })fromBase64any(str, { format = 'uint8', padding = 'both' })@exodus/bytes/base32.jstoBase32(arr, { padding = false })toBase32hex(arr, { padding = false })fromBase32(str, { format = 'uint8', padding = 'both' })fromBase32hex(str, { format = 'uint8', padding = 'both' })@exodus/bytes/bech32.jsgetPrefix(str, limit = 90)toBech32(prefix, bytes, limit = 90)fromBech32(str, limit = 90)toBech32m(prefix, bytes, limit = 90)fromBech32m(str, limit = 90)@exodus/bytes/base58.jstoBase58(arr)fromBase58(str, format = 'uint8')toBase58xrp(arr)fromBase58xrp(str, format = 'uint8')@exodus/bytes/base58check.jsOn non-Node.js, requires peer dependency @exodus/crypto to be installed.
async toBase58check(arr)toBase58checkSync(arr)async fromBase58check(str, format = 'uint8')fromBase58checkSync(str, format = 'uint8')makeBase58check(hashAlgo, hashAlgoSync)@exodus/bytes/wif.jsasync fromWifString(string, version)fromWifStringSync(string, version)async toWifString({ version, privateKey, compressed })toWifStringSync({ version, privateKey, compressed })@exodus/bytes/encoding.jsImplements the Encoding standard: TextDecoder, TextEncoder, some hooks (see below).
import { TextDecoder, TextDecoder } from '@exodus/bytes/encoding.js'
// Hooks for standards
import { getBOMEncoding, legacyHookDecode, labelToName, normalizeEncoding } from '@exodus/bytes/encoding.js'
new TextDecoder(label = 'utf-8', { fatal = false, ignoreBOM = false })TextDecoder implementation/polyfill.
new TextEncoder()TextEncoder implementation/polyfill.
labelToName(label)Implements get an encoding from a string label.
Converts an encoding label to its name, as a case-sensitive string.
If an encoding with that label does not exist, returns null.
All encoding names are also valid labels for corresponding encodings.
normalizeEncoding(label)Converts an encoding label to its name, as an ASCII-lowercased string.
If an encoding with that label does not exist, returns null.
This is the same as decoder.encoding getter,
except that it:
replacement encoding and its
labelsnullIt is identical to:
labelToName(label)?.toLowerCase() ?? null
All encoding names are also valid labels for corresponding encodings.
getBOMEncoding(input)Implements BOM sniff legacy hook.
Given a TypedArray or an ArrayBuffer instance input, returns either of:
'utf-8', if input starts with UTF-8 byte order mark.'utf-16le', if input starts with UTF-16LE byte order mark.'utf-16be', if input starts with UTF-16BE byte order mark.null otherwise.legacyHookDecode(input, fallbackEncoding = 'utf-8')Implements decode legacy hook.
Given a TypedArray or an ArrayBuffer instance input and an optional fallbackEncoding
encoding label,
sniffs encoding from BOM with fallbackEncoding fallback and then
decodes the input using that encoding, skipping BOM if it was present.
Notes:
fallbackEncoding option per spec.
Use with care.This method is similar to the following code, except that it doesn't support encoding labels and only expects lowercased encoding name:
new TextDecoder(getBOMEncoding(input) ?? fallbackEncoding).decode(input)
@exodus/bytes/encoding-lite.jsimport { TextDecoder, TextDecoder } from '@exodus/bytes/encoding-lite.js'
// Hooks for standards
import { getBOMEncoding, legacyHookDecode, labelToName, normalizeEncoding } from '@exodus/bytes/encoding-lite.js'
The exact same exports as @exodus/bytes/encoding.js are also exported as
@exodus/bytes/encoding-lite.js, with the difference that the lite version does not load
multi-byte TextDecoder encodings by default to reduce bundle size 10x.
The only affected encodings are: gbk, gb18030, big5, euc-jp, iso-2022-jp, shift_jis
and their labels when used with TextDecoder.
Legacy single-byte encodingds are loaded by default in both cases.
TextEncoder and hooks for standards (including labelToName / normalizeEncoding) do not have any behavior
differences in the lite version and support full range if inputs.
To avoid inconsistencies, the exported classes and methods are exactly the same objects.
> lite = require('@exodus/bytes/encoding-lite.js')
[Module: null prototype] {
TextDecoder: [class TextDecoder],
TextEncoder: [class TextEncoder],
getBOMEncoding: [Function: getBOMEncoding],
labelToName: [Function: labelToName],
legacyHookDecode: [Function: legacyHookDecode],
normalizeEncoding: [Function: normalizeEncoding]
}
> new lite.TextDecoder('big5').decode(Uint8Array.of(0x25))
Uncaught:
Error: Legacy multi-byte encodings are disabled in /encoding-lite.js, use /encoding.js for full encodings range support
> full = require('@exodus/bytes/encoding.js')
[Module: null prototype] {
TextDecoder: [class TextDecoder],
TextEncoder: [class TextEncoder],
getBOMEncoding: [Function: getBOMEncoding],
labelToName: [Function: labelToName],
legacyHookDecode: [Function: legacyHookDecode],
normalizeEncoding: [Function: normalizeEncoding]
}
> full.TextDecoder === lite.TextDecoder
true
> new full.TextDecoder('big5').decode(Uint8Array.of(0x25))
'%'
> new lite.TextDecoder('big5').decode(Uint8Array.of(0x25))
'%'
FAQs
Various operations on Uint8Array data
The npm package @exodus/bytes receives a total of 32,315,491 weekly downloads. As such, @exodus/bytes popularity was classified as popular.
We found that @exodus/bytes demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.

Research
/Security News
A new GhostAction wave hits hundreds of GitHub repos, expanding CI/CD secret theft to cloud and AI credentials in source code and git history.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.