Guide

Hashing

Pick an algorithm by name and hash text or bytes. Hex and base64 and base64url or raw bytes out

One method

ts
import { create } from "@agntn/hashes";

const sha256 = create("sha256");

sha256.hash("abc").digest;
// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
sha256.hash("abc", { encoding: "base64" }).digest;
// "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="
sha256.hash("abc", { encoding: "base64url" }).digest;
// "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
sha256.hash("abc", { encoding: "binary" }).digest;
// Uint8Array(32)

That's the whole API for most people. hash(input, options) on any algorithm, the same result shape back. hex is the default. base64url drops the padding, the way JWTs write it. binary hands you the raw Uint8Array, which is what you want before you feed the digest into another hash.

Text or bytes

A string is read as UTF-8. A Uint8Array is hashed as it is. The difference matters the moment your input is already bytes written as hex, like a public key:

ts
const key = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";

create("hash160").hash(key).digest;
// "b170e26bb62fb9dbfa28bb2f5e320c96459a2bed", the 66 characters, hashed as text
create("hash160").hash(Uint8Array.fromHex(key)).digest;
// "751e76e8199196d454941c45d1b3a323f1433bd6", the 33 bytes, which is what Bitcoin hashes

The first one is a perfectly valid digest of a string nobody meant. That's the classic bug, and it's silent. The library takes bytes, so pass bytes. The CLI and the agent tools can't pass a Uint8Array, so they take --input-encoding hex and inputEncoding: "hex" instead and decode it for you. See CLI and Agents.

Non-ASCII text is fine, UTF-8 is UTF-8. "żółw" and new TextEncoder().encode("żółw") give the same digest.

Names

create() is strict: the exact registry key or an UnknownAlgorithmError. For names a human typed, use resolveAlgorithm(). It lowercases, turns spaces and underscores into hyphens and then looks the result up, nothing smarter.

ts
import { algorithms, has, resolveAlgorithm } from "@agntn/hashes";

resolveAlgorithm("SHA3_256").name(); // "sha3-256"
resolveAlgorithm("Blake2b 256").name(); // "blake2b-256"
has("sha224"); // false, unless you registered one
algorithms(); // every key, built-ins in listing order, then yours

No fuzzy match on purpose. sha3 could mean four different things, and guessing is how you get a wrong digest that looks right.

What an algorithm says about itself

info() is how an algorithm describes itself, and the site, the CLI and the tools all read it:

ts
create("keccak256").info();
// {
//   name: "keccak256",
//   label: "Keccak-256",
//   family: "cryptographic",
//   digestLength: 32,
//   hmac: true,
//   options: [{ name: "encoding", ... }, { name: "key", ... }],
//   securityNote: "128-bit collision resistance, ...",
//   ...
// }

options lists everything the algorithm takes, with type, default and description. A KDF declares salt and its costs there, xxHash its seed. The CLI turns them into flags and the tools take them as parameters, so an option you add in one place reaches all of them.

Families

Four of them, and they mean what they say:

  • cryptographic. SHA-2, SHA-3, Keccak, BLAKE, RIPEMD-160 and the Bitcoin compositions. Use these when it matters.
  • legacy. MD5 and SHA-1. Broken for collisions, still in every checksum file and inside Git. Fine for matching a checksum somebody else published. Not fine for anything you'd sign.
  • non-cryptographic. CRC-32, CRC-16/XMODEM, xxHash, FNV-1a. Fast, tiny, trivial to collide on purpose. Tables, dedup, error detection.
  • password. scrypt and PBKDF2. Slow on purpose, and they need a salt. They have their own page.

hashes algorithms -f legacy and hash_algorithms with family list one of them.