Getting Started
Why this exists
Ask a model for the SHA-256 of a string. It gives you 64 hex characters, very confidently, and they're wrong. Hashing is the one thing a language model can't fake, so it should call something that actually hashes. @agntn/hashes is that something, and it's the same thing in your TypeScript and in your terminal.
It also has the hashes chains quietly depend on. Keccak-256 with its pre-SHA-3 padding, HASH160, double SHA-256, BLAKE2b cut to 32 or 28 bytes, SHA-512Half. Those are the ones people get subtly wrong, and subtly wrong means a different address.
No HTTP, no keys, no telemetry, no hashing dependency. Every digest is plain TypeScript in src/core/, and the library imports nothing from node:*. That's why this site can run it in your browser.
Install
pnpm add @agntn/hashes
Node.js 26 or newer. It needs Uint8Array with native hex and base64, and 26 is where that stopped being a question.
First call
import { create } from "@agntn/hashes";
const result = create("sha256").hash("abc");
result.digest; // "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
result.digestLength; // 32
create(name) wants the exact registry key and hands back one cached instance. resolveAlgorithm("SHA3_256") forgives case, spaces and underscores, then wants an exact match too. No prefixes, no fuzzy guess. A typo is an UnknownAlgorithmError, and its message lists every name that does exist.
The shape
Every hash() returns this, whatever the algorithm:
interface HashResult {
digest: string | Uint8Array; // bytes only with encoding "binary"
algorithm: string; // "sha256"
operation: "hash" | "hmac";
encoding: "hex" | "base64" | "base64url" | "binary";
digestLength: number; // bytes of the raw digest
options: Record<string, unknown>; // what the digest depends on: encoding, salt, cost, seed
}
options is the part people skip and then regret. For scrypt it holds the salt it drew and the cost it ran with, and without those the digest is a string that looks random and that you can never reproduce.
What ships
| Options | Security | ||||
|---|---|---|---|---|---|
| SHA-256sha256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| SHA-384sha384 | Cryptographic | 384-bit | HMAC yes | no options | 192-bit collision resistance |
| SHA-512sha512 | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| SHA-512Halfsha512-half | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| SHA3-256sha3-256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| SHA3-512sha3-512 | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| Keccak-256keccak256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| BLAKE2bblake2b | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| BLAKE2b-256blake2b-256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| BLAKE2b-224blake2b-224 | Cryptographic | 224-bit | HMAC no | no options | 112-bit collision resistance |
| BLAKE2sblake2s | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| BLAKE3blake3 | Cryptographic | 256-bit | HMAC no | no options | 128-bit security against collisions and preimages |
| BLAKE-256blake256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| RIPEMD-160ripemd160 | Cryptographic | 160-bit | HMAC yes | no options | 80-bit collision resistance |
| HASH160hash160 | Cryptographic | 160-bit | HMAC no | no options | 80-bit collision resistance |
| HASH256 (double SHA-256)hash256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| MD5md5 | Legacy | 128-bit | HMAC yes | no options | BROKEN: collision attacks known since 2004 |
| SHA-1sha1 | Legacy | 160-bit | HMAC yes | no options | BROKEN: practical collision attack (SHAttered) |
| CRC-32crc32 | Non-cryptographic | 32-bit | HMAC no | no options | NOT for security: error-detection checksum only |
| CRC-16/XMODEMcrc16-xmodem | Non-cryptographic | 16-bit | HMAC no | no options | NOT for security |
| xxHash (XXH64)xxhash | Non-cryptographic | 64-bit | HMAC no | seed? | NOT for security: fast hash for hash tables |
| FNV-1a (64-bit)fnv1a | Non-cryptographic | 64-bit | HMAC no | no options | NOT for security: simple hash for hash tables |
| scryptscrypt | Password | variable | HMAC no | salt?, N?, r?, p?, keyLength? | Memory-hard KDF |
| PBKDF2pbkdf2 | Password | variable | HMAC no | salt?, iterations?, digest?, keyLength? | OWASP Password Storage Cheat Sheet: >=600000 iterations with HMAC-SHA256 |
Every row comes from create(name).info(), read in your browser. The HMAC column says which ones take a key. The rest refuse it with an error, they don't quietly hash without it.
Errors
import { HashError, InvalidOptionError, UnknownAlgorithmError, create } from "@agntn/hashes";
try {
create("scrypt").hash("password", { N: 1000 });
} catch (error) {
if (error instanceof InvalidOptionError) {
error.option; // "N"
error.reason; // "must be a power of 2 and >= 2"
}
}
A name nobody registered is an UnknownAlgorithmError. A bad value is an InvalidOptionError with the option, the value and the reason. A required one left out is a MissingOptionError. Anything else an algorithm throws gets wrapped by normalizeError into a HashError that names the algorithm. The CLI prints the message on one line and exits 1. No stack trace, the input was wrong, not you.
Next
- Hashing: encodings, text versus bytes, names.
- HMAC and verify: keys, and comparing digests without a timing leak.
- Salted KDFs: scrypt and PBKDF2, and why they print the salt.
- CLI: the
hashescommand. - Agents: the same tools over MCP, Pi, OMP and the AI SDK.
- Custom algorithms: extend
FixedHash, register it. - Playground: every algorithm, in the page.