mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmSecurityupdated 08 Aug 2026

ethereum-cryptography

ethereum-cryptography is a pure JavaScript collection of the low-level primitives commonly needed by Ethereum software: Keccak and other hashes, secp256k1 signatures, BLS and BN254 curves, secure random bytes, BIP39 mnemonics, BIP32 hierarchical keys, PBKDF2, scrypt, AES, and byte conversion helpers. Version 3.2.0 mostly re-exports pinned noble and scure implementations through stable subpaths. It is cryptographic plumbing, not a wallet, signer, transaction encoder, key vault, or Ethereum RPC client.

Verdict

A strong low-level default for Ethereum implementers who know exactly which bytes and domain rules they need. Most application teams should install viem or ethers instead, and no team should treat the audit badge as a substitute for protocol and key-management review.

API stability4/5Version 3 keeps a disciplined subpath-per-primitive design and pins its underlying noble and scure APIs, limiting surprise from transitive upgrades. The README also provides explicit migration notes: v2 changed the secp256k1 return types and recovery API, while v3 removed a utils export and made AES methods synchronous. Those are real major-version breaks, but they are documented and aligned with semantic versioning.
Docs5/5The README covers every exported primitive with imports and examples, explains Uint8Array encoding, lists every subpath, documents browser bundler setup, and spends unusual space on AES key derivation, modes, padding, IV uniqueness, and error leakage. It also links the Cure53 audit and upstream noble material. Users still need Ethereum protocol specifications because address checksums, signed-message framing, transactions, and key custody are intentionally outside scope.
Maintenance5/5The current 3.2.0 release was published in April 2025, the repository was pushed in May 2026, and GitHub reports zero open issues and pull requests. The package is maintained under the ethereum organization, pins exact versions of six focused dependencies, tests major bundlers, and documents a security audit. Maintenance quality is high, though applications should remember that the named full-stack audit dates to January 2022.
Ecosystem5/5The package recorded 4,032,000 downloads for July 31 through August 6, 2026 and provides the cryptographic base used across Ethereum JavaScript software. Its noble and scure foundations are shared with many other security-sensitive projects, and dual ESM/CommonJS exports ease adoption. The ecosystem is intentionally primitive-level; wallet adapters, RPC transports, ABI codecs, transaction builders, and custody integrations belong to libraries such as viem and ethers.

Use it if

  • You are implementing Ethereum infrastructure and need audited pure-JavaScript primitives that behave in supported browsers and Node on x86 or arm64
  • You want one version-pinned surface for noble hashes, curves, ciphers and scure BIP32/BIP39 instead of coordinating those packages yourself
  • You need direct byte-level control over Keccak, secp256k1, BLS12-381, BN254, mnemonic seeds, or hierarchical keys
  • You can keep secret-key handling and protocol encoding in a separately reviewed layer and will import only the submodules you use
Skip it if

Setup reality

There are no native builds, credentials, daemon processes, or config files. Install version 3.2.0 exactly if you follow the project's own supply-chain advice, then import a specific path such as ethereum-cryptography/keccak.js or ethereum-cryptography/secp256k1.js. Do not expect import { keccak256 } from 'ethereum-cryptography' to be the supported style; the README deliberately separates primitives so web bundlers cannot accidentally retain the whole suite. The package provides both ESM and CommonJS export conditions and includes TypeScript declarations. Its manifest supports Node ^14.21.3 or Node 16 and newer and declares npm 9 or newer. Browser builds require secure crypto.getRandomValues; Node uses crypto.randomBytes, and random generation throws rather than falling back when neither exists. Rollup needs the CommonJS and node-resolve plugins with browser: true and preferBuiltins: false, according to the browser caveat. Data crosses the API as Uint8Array or accepted hex strings, not Node Buffer, so encode text with utf8ToBytes and render bytes with bytesToHex. That boundary is a common source of double-hex and wrong-message bugs. The low-level layer does not add Ethereum protocol framing: secp256k1.sign signs the digest you supply, Keccak is not standardized SHA3-256, and deriving an address still requires dropping the uncompressed public-key prefix, hashing, and taking the final 20 bytes. BIP39 wordlists are separate imports to keep bundles smaller. Asynchronous PBKDF2 and scrypt avoid the explicitly discouraged sync browser calls, but you still need reviewed parameters, a unique random salt, progress or worker strategy, and denial-of-service limits. AES callers must generate a unique IV, preserve it with ciphertext, derive keys from passwords through a KDF, avoid returning detailed decrypt errors, and add authentication outside this wrapper. The January 2022 Cure53 audit is valuable evidence for the rewritten stack, not a guarantee that your protocol or the current dependency set is safe.

Patterns

Hash UTF-8 data with Ethereum Keccak-256hash-keccak256

import { keccak256 } from 'ethereum-cryptography/keccak.js';
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils.js';

const digest = keccak256(utf8ToBytes('hello'));
console.log(bytesToHex(digest));

Keccak-256 is the Ethereum hash and is not interchangeable with standardized SHA3-256. Always make the input encoding explicit.

Compute a SHA-256 digesthash-sha256

import { sha256 } from 'ethereum-cryptography/sha256.js';
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils.js';

const digestHex = bytesToHex(sha256(utf8ToBytes('artifact contents')));

Hash bytes, not a visual representation of bytes. Hashing the UTF-8 characters of a hex string produces a different digest from hashing decoded hex.

Generate a valid secp256k1 private keygenerate-private-key

import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
import { bytesToHex } from 'ethereum-cryptography/utils.js';

const privateKey = secp256k1.utils.randomPrivateKey();
const publicKey = secp256k1.getPublicKey(privateKey, false);
console.log(bytesToHex(publicKey));

Do not log a private key in real code. The curve helper rejects invalid scalars and uses the package's secure randomness backend.

Derive a lowercase Ethereum addressderive-ethereum-address

import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
import { keccak256 } from 'ethereum-cryptography/keccak.js';
import { bytesToHex } from 'ethereum-cryptography/utils.js';

const publicKey = secp256k1.getPublicKey(privateKey, false);
const addressBytes = keccak256(publicKey.slice(1)).slice(-20);
const address = `0x${bytesToHex(addressBytes)}`;

This returns a lowercase address, not an EIP-55 checksum address. Drop the 0x04 uncompressed-key prefix before hashing.

Sign and verify a 32-byte digestsign-and-verify-digest

import { secp256k1 } from 'ethereum-cryptography/secp256k1.js';
import { keccak256 } from 'ethereum-cryptography/keccak.js';
import { utf8ToBytes } from 'ethereum-cryptography/utils.js';

const digest = keccak256(utf8ToBytes('domain-separated message'));
const signature = secp256k1.sign(digest, privateKey);
const publicKey = secp256k1.getPublicKey(privateKey);
const valid = secp256k1.verify(signature, digest, publicKey);

This is raw digest signing. It does not add the EIP-191 personal-sign prefix, EIP-712 domain data, or transaction encoding.

Recover the public key from a signaturerecover-public-key

const signature = secp256k1.sign(digest, privateKey);
const recoveredPoint = signature.recoverPublicKey(digest);
const recoveredPublicKey = recoveredPoint.toRawBytes(false);
const expectedPublicKey = secp256k1.getPublicKey(privateKey, false);

Version 2 moved recovery onto the Signature instance. Keep the recovery bit carried by the object returned from sign.

Generate and validate an English BIP39 mnemonicgenerate-mnemonic

import { generateMnemonic, validateMnemonic } from 'ethereum-cryptography/bip39/index.js';
import { wordlist } from 'ethereum-cryptography/bip39/wordlists/english.js';

const mnemonic = generateMnemonic(wordlist);
if (!validateMnemonic(mnemonic, wordlist)) throw new Error('invalid mnemonic');

The wordlist is a separate import. Never log or send the mnemonic; anyone who obtains it can derive the wallet keys.

Derive an Ethereum BIP44 child keyderive-hd-wallet

import { mnemonicToSeed } from 'ethereum-cryptography/bip39/index.js';
import { wordlist } from 'ethereum-cryptography/bip39/wordlists/english.js';
import { validateMnemonic } from 'ethereum-cryptography/bip39/index.js';
import { HDKey } from 'ethereum-cryptography/hdkey.js';

if (!validateMnemonic(mnemonic, wordlist)) throw new Error('invalid mnemonic');
const seed = await mnemonicToSeed(mnemonic, passphrase);
const account = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/0");
if (!account.privateKey) throw new Error('private key unavailable');

A BIP39 passphrase changes every derived key. Losing or mistyping it is indistinguishable from using the wrong wallet.

Derive key bytes with asynchronous PBKDF2derive-key-pbkdf2

import { pbkdf2 } from 'ethereum-cryptography/pbkdf2.js';
import { getRandomBytes } from 'ethereum-cryptography/random.js';
import { utf8ToBytes } from 'ethereum-cryptography/utils.js';

const salt = await getRandomBytes(16);
const key = await pbkdf2(utf8ToBytes(password), salt, 131072, 32, 'sha256');

Store the salt and reviewed parameters with the derived output. Parameter suitability depends on your threat model and hardware, so benchmark and revisit it.

Derive key bytes with asynchronous scryptderive-key-scrypt

import { scrypt } from 'ethereum-cryptography/scrypt.js';
import { getRandomBytes } from 'ethereum-cryptography/random.js';
import { utf8ToBytes } from 'ethereum-cryptography/utils.js';

const salt = await getRandomBytes(16);
const key = await scrypt(utf8ToBytes(password), salt, 262144, 8, 1, 32);

These parameters consume substantial CPU and memory. Bound concurrent requests and do not let an unauthenticated caller choose N, r, or p.

Decrypt an existing AES-CBC payloaddecrypt-legacy-aes

import { decrypt } from 'ethereum-cryptography/aes.js';

function decryptLegacy(ciphertext, key, iv) {
  try {
    return decrypt(ciphertext, key, iv, 'aes-256-cbc', true);
  } catch {
    throw new Error('decryption failed');
  }
}

CBC does not authenticate ciphertext, and the README recommends it only for existing data. Return one generic error so padding details do not leak.

Sign and verify with BLS12-381sign-bls-message

import { bls12_381 as bls } from 'ethereum-cryptography/bls.js';

const privateKey = bls.utils.randomPrivateKey();
const publicKey = bls.getPublicKey(privateKey);
const signature = bls.sign(messageBytes, privateKey);
const valid = bls.verify(signature, messageBytes, publicKey);

BLS protocols depend on ciphersuite, domain-separation tag, key validation, and proof-of-possession choices. Match the protocol specification, not just this round trip.

Alternatives

PackageRegistryPick it when
viemnpmChoose it for typed Ethereum RPC, accounts, transaction and message utilities instead of assembling protocol behavior from primitives
ethersnpmChoose it for a broad wallet, provider, contract, ABI, signing, and utility toolkit with familiar Ethereum conventions
@noble/hashesnpmChoose it directly when you only need audited hash and KDF implementations and do not need the Ethereum compatibility surface
@noble/curvesnpmChoose it directly when curve APIs are the whole requirement and you prefer following noble's current releases yourself