Fastest 5KB JS implementation of secp256k1 signatures & ECDH.
- ✍️ ECDSA signatures compliant with RFC6979
- ➰ Schnorr signatures compliant with BIP340
- 🤝 Elliptic Curve Diffie-Hellman ECDH
- 🔒 Supports hedged signatures guarding against fault attacks
- 🪶 4.94KB (gzipped) - 10-25x smaller than similar libraries
The module is a sister project of noble-curves. Use noble-secp256k1 if you need smaller attack surface & better auditability. Switch to noble-curves (drop-in) if you need features like MSM, DER encoding, custom point precomputes.
898-byte version of the library is available for learning purposes in test/misc/1kb.min.js,
it was created for the article Learning fast elliptic-curve cryptography.
noble-cryptography — high-security, easily auditable set of contained cryptographic libraries and tools.
- Zero or minimal dependencies
- Highly readable TypeScript / JS code
- PGP-signed releases and transparent NPM builds
- All libraries: ciphers, curves, hashes, post-quantum, 5kb secp256k1 / ed25519
- Check out the homepage for reading resources, documentation, and apps built with noble
npm install @noble/secp256k1
deno add jsr:@noble/secp256k1
We support all major platforms and runtimes. For React Native, additional polyfills are needed: see below.
import * as secp from '@noble/secp256k1';
(async () => {
const { secretKey, publicKey } = secp.keygen();
// const publicKey = secp.getPublicKey(secretKey);
const msg = new TextEncoder().encode('hello noble');
const sig = await secp.signAsync(msg, secretKey);
const isValid = await secp.verifyAsync(sig, msg, publicKey);
})();
// ECDH, key recovery
(async () => {
const alice = secp.keygen();
const bob = secp.keygen();
const shared = secp.getSharedSecret(alice.secretKey, bob.publicKey);
// recovery
const sigr = await secp.signAsync(msg, alice.secretKey, { format: 'recovered' });
const publicKey2 = secp.recoverPublicKey(sigr, msg);
})();
// Schnorr signatures from BIP340
(async () => {
const schnorr = secp.schnorr;
const { secretKey, publicKey } = schnorr.keygen();
const msg = new TextEncoder().encode('hello noble');
const sig = await schnorr.signAsync(msg, secretKey);
const isValid = await schnorr.verifyAsync(sig, msg, publicKey);
})();Only async methods are available by default, to keep the library dependency-free. To enable sync methods:
import { hmac } from '@noble/hashes/hmac.js';
import { sha256 } from '@noble/hashes/sha2.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;React Native does not provide secure getRandomValues by default. This can't be securely polyfilled from our end, so one will need a RN-specific compile-time dep.
import 'react-native-get-random-values';
import { hmac } from '@noble/hashes/hmac.js';
import { sha256 } from '@noble/hashes/sha2.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
secp.hashes.hmacSha256Async = async (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256Async = async (msg) => sha256(msg);There are 4 main methods, which accept Uint8Array-s:
keygen()getPublicKey(secretKey)sign(messageHash, secretKey)andsignAsync(messageHash, secretKey)verify(signature, messageHash, publicKey)andverifyAsync(signature, messageHash, publicKey)
import * as secp from '@noble/secp256k1';
(async () => {
const keys = secp.keygen();
const { secretKey, publicKey } = keys;
})();import * as secp from '@noble/secp256k1';
const secretKey = secp.utils.randomSecretKey();
const pubKey33b = secp.getPublicKey(secretKey);
// Variants
const pubKey65b = secp.getPublicKey(secretKey, false);
const pubKeyPoint = secp.Point.fromBytes(pubKey65b);
const samePoint = pubKeyPoint.toBytes();Generates 33-byte compressed (default) or 65-byte public key from 32-byte private key.
import * as secp from '@noble/secp256k1';
const { secretKey } = secp.keygen();
const msg = 'hello noble';
const sig = secp.sign(msg, secretKey);
// async
const sigB = await secp.signAsync(msg, secretKey);
// recovered, allows `recoverPublicKey(sigR, msg)`
const sigR = secp.sign(msg, secretKey, { format: 'recovered' });
// custom hash
import { keccak256 } from '@noble/hashes/sha3.js';
const sigH = secp.sign(keccak256(msg), secretKey, { prehash: false });
// hedged sig
const sigC = secp.sign(msg, secretKey, { extraEntropy: true });
const sigC2 = secp.sign(msg, secretKey, { extraEntropy: Uint8Array.from([0xca, 0xfe]) });
// malleable sig
const sigD = secp.sign(msg, secretKey, { lowS: false });Generates low-s deterministic-k RFC6979 ECDSA signature.
- Message will be hashed with sha256. If you want to use a different hash function,
make sure to use
{ prehash: false }. extraEntropy: trueenables hedged signatures. They incorporate extra randomness into RFC6979 (described in section 3.6), to provide additional protection against fault attacks. Check out blog post Deterministic signatures are not your friends. Even if their RNG is broken, they will fall back to determinism.- Default behavior
lowS: trueprohibits signatures which have (sig.s >= CURVE.n/2n) and is compatible with BTC/ETH. SettinglowS: falseallows to create malleable signatures, which is default openssl behavior. Non-malleable signatures can still be successfully verified in openssl.
import * as secp from '@noble/secp256k1';
const { secretKey, publicKey } = secp.keygen();
const msg = 'hello noble';
const sig = secp.sign(msg, secretKey);
const isValid = secp.verify(sig, msg, publicKey);
// custom hash
import { keccak256 } from '@noble/hashes/sha3.js';
const sigH = secp.sign(keccak256(msg), secretKey, { prehash: false });Verifies ECDSA signature.
- Message will be hashed with sha256. If you want to use a different hash function,
make sure to use
{ prehash: false }. - Default behavior
lowS: trueprohibits malleable signatures which have (sig.s >= CURVE.n/2n) and is compatible with BTC / ETH. SettinglowS: falseallows to create signatures, which is default openssl behavior.
import * as secp from '@noble/secp256k1';
const alice = secp.keygen();
const bob = secp.keygen();
const shared33b = secp.getSharedSecret(alice.secretKey, bob.publicKey);
const shared65b = secp.getSharedSecret(bob.secretKey, alice.publicKey, false);
const sharedPoint = secp.Point.fromBytes(bob.publicKey).multiply(
secp.etc.secretKeyToScalar(alice.secretKey)
);Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between key A and different key B.
import * as secp from '@noble/secp256k1';
const { secretKey, publicKey } = secp.keygen();
const msg = 'hello noble';
const sigR = secp.sign(msg, secretKey, { format: 'recovered' });
const publicKey2 = secp.recoverPublicKey(sigR, msg);
// custom hash
import { keccak256 } from '@noble/hashes/sha3.js';
const sigR = secp.sign(keccak256(msg), secretKey, { format: 'recovered', prehash: false });
const publicKey2 = secp.recoverPublicKey(sigR, keccak256(msg), { prehash: false });Recover public key from Signature instance with recovery bit set.
import { schnorr } from '@noble/secp256k1';
const { secretKey, publicKey } = schnorr.keygen();
const msg = new TextEncoder().encode('hello noble');
const sig = schnorr.sign(msg, secretKey);
const isValid = schnorr.verify(sig, msg, publicKey);
const sig = await schnorr.signAsync(msg, secretKey);
const isValid = await schnorr.verifyAsync(sig, msg, publicKey);Schnorr signatures compliant with BIP340 are supported.
A bunch of useful utilities are also exposed:
import * as secp from '@noble/secp256k1';
const { bytesToHex, hexToBytes, concatBytes, mod, invert, randomBytes } = secp.etc;
const { isValidSecretKey, isValidPublicKey, randomSecretKey } = secp.utils;
const { Point } = secp;
console.log(Point.CURVE(), Point.BASE);
/*
class Point {
static BASE: Point;
static ZERO: Point;
readonly X: bigint;
readonly Y: bigint;
readonly Z: bigint;
constructor(X: bigint, Y: bigint, Z: bigint);
static CURVE(): WeierstrassOpts<bigint>;
static fromAffine(ap: AffinePoint): Point;
static fromBytes(bytes: Bytes): Point;
static fromHex(hex: string): Point;
get x(): bigint;
get y(): bigint;
equals(other: Point): boolean;
is0(): boolean;
negate(): Point;
double(): Point;
add(other: Point): Point;
subtract(other: Point): Point;
multiply(n: bigint): Point;
multiplyUnsafe(scalar: bigint): Point;
toAffine(): AffinePoint;
assertValidity(): Point;
toBytes(isCompressed?: boolean): Bytes;
toHex(isCompressed?: boolean): string;
}
*/The module is production-ready.
We cross-test against sister project noble-curves, which was audited and provides improved security.
- The current version has not been independently audited. It is a rewrite of v1, which has been audited by cure53 in Apr 2021: PDF (funded by Umbra.cash & community).
- It's being fuzzed in a separate repository
We're targetting algorithmic constant time. JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve timing attack resistance in a scripting language. Which means any other JS library can't have constant-timeness. Even statically typed Rust, a language without GC, makes it harder to achieve constant-time for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages.
- Commits are signed with PGP keys to prevent forgery. Be sure to verify the commit signatures
- Releases are made transparently through token-less GitHub CI and Trusted Publishing. Be sure to verify the provenance logs for authenticity.
- Rare releasing is practiced to minimize the need for re-audits by end-users.
- Dependencies are minimized and strictly pinned to reduce supply-chain risk.
- We use as few dependencies as possible.
- Version ranges are locked, and changes are checked with npm-diff.
- Dev dependencies are excluded from end-user installs; they’re only used for development and build steps.
For this package, there are 0 dependencies; and a few dev dependencies:
- noble-hashes provides cryptographic hashing functionality
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation
We rely on the built-in
crypto.getRandomValues,
which is considered a cryptographically secure PRNG.
Browsers have had weaknesses in the past - and could again - but implementing a userspace CSPRNG is even worse, as there’s no reliable userspace source of high-quality entropy.
Cryptographically relevant quantum computer, if built, will allow to break elliptic curve cryptography (both ECDSA / EdDSA & ECDH) using Shor's algorithm.
Consider switching to newer / hybrid algorithms, such as SPHINCS+. They are available in noble-post-quantum.
NIST prohibits classical cryptography (RSA, DSA, ECDSA, ECDH) after 2035. Australian ASD prohibits it after 2030.
npm run bench
Benchmarks measured with Apple M4. noble-curves enable faster performance.
keygen x 7,643 ops/sec @ 130μs/op
sign x 7,620 ops/sec @ 131μs/op
verify x 823 ops/sec @ 1ms/op
getSharedSecret x 707 ops/sec @ 1ms/op
recoverPublicKey x 790 ops/sec @ 1ms/op
signAsync x 4,874 ops/sec @ 205μs/op
verifyAsync x 811 ops/sec @ 1ms/op
Point.fromBytes x 13,656 ops/sec @ 73μs/op
v3 brings the package closer to noble-curves v2.
- Add Schnorr signatures
- Most methods now expect Uint8Array, string hex inputs are prohibited
- Add
keygen,keygenAsyncmethod - sign, verify: Switch to prehashed messages. Instead of
messageHash, the methods now expect unhashed message.
To bring back old behavior, use option
{prehash: false} - sign, verify: Switch to Uint8Array signatures (format: 'compact') by default.
- verify: der format must be explicitly specified in
{format: 'der'}. This reduces malleability - verify: prohibit Signature-instance signature. User must now always do
signature.toBytes() - Node v20.19 is now the minimum required version
- Various small changes for types
- etc: hashes are now set in
hashesobject. Also sha256 needs to be set now forprehash: true:
// before
etc.hmacSha256Sync = (key, ...messages) => hmac(sha256, key, etc.concatBytes(...messages));
etc.hmacSha256Async = (key, ...messages) => Promise.resolve(etc.hmacSha256Sync(key, ...messages));
// after
hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
hashes.sha256 = sha256;
hashes.hmacSha256Async = async (key, msg) => hmac(sha256, key, msg);
hashes.sha256Async = async (msg) => sha256(msg);v2 improves security and reduces attack surface. The goal of v2 is to provide minimum possible JS library which is safe and fast.
- Disable some features to ensure 4x smaller than v1, 5KB bundle size:
- The features are now a part of noble-curves, switch to curves if you need them. Curves is drop-in replacement.
- DER encoding: toDERHex, toDERRawBytes, signing / verification of DER sigs
- Schnorr signatures
- Support for environments which don't support bigint literals
- Common.js support
- Support for node.js 18 and older without shim
- Using
utils.precompute()for non-base point
getPublicKey- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument
isCompressedtofalse:getPublicKey(priv, false)
sign- is now sync; use
signAsyncfor async version - now returns
Signatureinstance with{ r, s, recovery }properties canonicaloption was renamed tolowSrecoveredoption has been removed because recovery bit is always returned nowderoption has been removed. There are 2 options:- Use compact encoding:
fromCompact,toBytes,toCompactHex. Compact encoding is simply a concatenation of 32-byte r and 32-byte s. - If you must use DER encoding, switch to noble-curves (see above).
- Use compact encoding:
- is now sync; use
verifystrictoption was renamed tolowS
getSharedSecret- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument
isCompressedtofalse:getSharedSecret(a, b, false)
recoverPublicKey(msg, sig, rec)was changed tosig.recoverPublicKey(msg)numbertype for private keys have been removed: usebigintinsteadPoint(2d xy) has been changed toProjectivePoint(3d xyz)utilswere split intoutils(same api as in noble-curves) andetc(hmacSha256Syncand others)
npm install && npm run build && npm testwill build the code and run tests.npm run benchwill run benchmarksnpm run build:releasewill build single non-module file
See paulmillr.com/noble for useful resources, articles, documentation and demos related to the library.
The MIT License (MIT)
Copyright (c) 2019 Paul Miller (https://paulmillr.com)
See LICENSE file.