@noble/secp256k1
Version:
Fastest 5KB JS implementation of secp256k1 ECDH & ECDSA signatures compliant with RFC6979
475 lines (377 loc) • 18.9 kB
Markdown
# noble-secp256k1
Fastest 5KB JS implementation of secp256k1 signatures & ECDH.
- ✍️ [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
signatures compliant with [RFC6979](https://www.rfc-editor.org/rfc/rfc6979)
- ➰ Schnorr
signatures compliant with [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)
- 🤝 Elliptic Curve Diffie-Hellman [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman)
- 🔒 Supports [hedged signatures](https://paulmillr.com/posts/deterministic-signatures/) guarding against fault attacks
- 🪶 4.94KB (gzipped) - 10-25x smaller than similar libraries
The module is a sister project of [noble-curves](https://github.com/paulmillr/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`](https://github.com/paulmillr/noble-secp256k1/blob/c38e57d17a2ecfdb9b8a80890a8e1a2cc140aa04/test/misc/1kb.min.js),
it was created for the article [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
### This library belongs to _noble_ 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](https://github.com/paulmillr/noble-ciphers),
[curves](https://github.com/paulmillr/noble-curves),
[hashes](https://github.com/paulmillr/noble-hashes),
[post-quantum](https://github.com/paulmillr/noble-post-quantum),
5kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
[ed25519](https://github.com/paulmillr/noble-ed25519)
- [Check out the homepage](https://paulmillr.com/noble/)
for reading resources, documentation, and apps built with noble
## Usage
> `npm install /secp256k1`
> `deno add jsr:/secp256k1`
We support all major platforms and runtimes. For React Native, additional polyfills are needed: see below.
```js
import * as secp from '/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);
const msg = new TextEncoder().encode('hello noble');
// recovery
const sigr = await secp.signAsync(msg, alice.secretKey, { format: 'recovered' });
const publicKey2 = await secp.recoverPublicKeyAsync(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);
})();
```
### Enabling synchronous methods
Only async methods are available by default, to keep the library dependency-free.
To enable sync methods:
> `npm install /hashes`
```ts
import * as secp from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
```
### React Native: polyfill getRandomValues and 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.
```ts
import 'react-native-get-random-values';
import * as secp from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/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);
```
## API
There are 4 main methods, which accept Uint8Array-s:
* `keygen()`
* `getPublicKey(secretKey)`
* `sign(messageHash, secretKey)` and `signAsync(messageHash, secretKey)`
* `verify(signature, messageHash, publicKey)` and `verifyAsync(signature, messageHash, publicKey)`
### keygen
```ts
import * as secp from '/secp256k1';
(async () => {
const keys = secp.keygen();
const { secretKey, publicKey } = keys;
})();
```
### getPublicKey
```ts
import * as secp from '/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.
### sign
```ts
import * as secp from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
import { keccak_256 } from '/hashes/sha3.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
const { secretKey } = secp.keygen();
const msg = new TextEncoder().encode('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' });
const sigH = secp.sign(keccak_256(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: true` enables 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](https://paulmillr.com/posts/deterministic-signatures/).
Even if their RNG is broken, they will fall back to determinism.
- Default behavior `lowS: true` prohibits signatures which have (sig.s >= CURVE.n/2n) and is compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures, which is default openssl behavior. Non-malleable signatures can still be successfully verified in openssl.
### verify
```ts
import * as secp from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
import { keccak_256 } from '/hashes/sha3.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
const { secretKey, publicKey } = secp.keygen();
const msg = new TextEncoder().encode('hello noble');
const sig = secp.sign(msg, secretKey);
const isValid = secp.verify(sig, msg, publicKey);
const sigH = secp.sign(keccak_256(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: true` prohibits malleable signatures which have (`sig.s >= CURVE.n/2n`) and
is compatible with BTC / ETH.
Setting `lowS: false` allows to create signatures, which is default openssl behavior.
### getSharedSecret
```ts
import * as secp from '/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.
### recoverPublicKey
```ts
import * as secp from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
import { keccak_256 } from '/hashes/sha3.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
const { secretKey, publicKey } = secp.keygen();
const msg = new TextEncoder().encode('hello noble');
const sigR = secp.sign(msg, secretKey, { format: 'recovered' });
const publicKey2 = secp.recoverPublicKey(sigR, msg);
const sigRH = secp.sign(keccak_256(msg), secretKey, { format: 'recovered', prehash: false });
const publicKeyH = secp.recoverPublicKey(sigRH, keccak_256(msg), { prehash: false });
```
Recover public key from Signature instance with `recovery` bit set.
### schnorr
```ts
import * as secp from '/secp256k1';
import { schnorr } from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;
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);
```
Async methods work without extra setup:
```ts
import { schnorr } from '/secp256k1';
const { secretKey, publicKey } = schnorr.keygen();
const msg = new TextEncoder().encode('hello noble');
const sigA = await schnorr.signAsync(msg, secretKey);
const isValidA = await schnorr.verifyAsync(sigA, msg, publicKey);
```
Schnorr
signatures compliant with [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)
are supported.
### utils
A bunch of useful **utilities** are also exposed:
```ts
import * as secp from '/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;
}
*/
```
## Security
The module is production-ready.
We cross-test against sister project [noble-curves](https://github.com/paulmillr/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](https://cure53.de/pentest-report_noble-lib.pdf) (funded by [Umbra.cash](https://umbra.cash) & community).
- It's being fuzzed [in a separate repository](https://github.com/paulmillr/fuzzing)
### Constant-timeness
We're targetting algorithmic constant time. _JIT-compiler_ and _Garbage Collector_ make "constant time"
extremely hard to achieve [timing attack](https://en.wikipedia.org/wiki/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](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
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.
### Supply chain security
- **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](https://docs.npmjs.com/generating-provenance-statements) 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](https://github.com/paulmillr/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
### Randomness
We rely on the built-in
[`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/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.
### Quantum computers
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](https://github.com/paulmillr/noble-post-quantum).
NIST prohibits classical cryptography (RSA, DSA, ECDSA, ECDH) [after 2035](https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf). Australian ASD prohibits it [after 2030](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography).
## Upgrading
### v2 to v3
v3 brings the package closer to noble-curves v2.
- Add Schnorr signatures
- Most methods now expect Uint8Array, string hex inputs are prohibited
- Add `keygen`, `keygenAsync` method
- 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 `hashes` object. Also sha256 needs to be set now for `prehash: true`:
```js
import { etc, hashes } from '/secp256k1';
import { hmac } from '/hashes/hmac.js';
import { sha256 } from '/hashes/sha2.js';
// 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);
```
### v1 to v2
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](https://github.com/paulmillr/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](#usage)
- 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 `isCompressed` to `false`: `getPublicKey(priv, false)`
- `sign`
- is now sync; use `signAsync` for async version
- now returns `Signature` instance with `{ r, s, recovery }` properties
- `canonical` option was renamed to `lowS`
- `recovered` option has been removed because recovery bit is always returned now
- `der` option has been removed. There are 2 options:
1. Use compact encoding: `fromCompact`, `toBytes`, `toCompactHex`.
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
2. If you must use DER encoding, switch to noble-curves (see above).
- `verify`
- `strict` option was renamed to `lowS`
- `getSharedSecret`
- now produce 33-byte compressed signatures by default
- to use old behavior, which produced 65-byte uncompressed keys, set
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
- `number` type for private keys have been removed: use `bigint` instead
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
- `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`hmacSha256Sync` and others)
## Contributing & testing
- `npm install && npm run build && npm test` will build the code and run tests.
- `npm run bench` will run benchmarks
- `npm run build:release` will build single non-module file
See [paulmillr.com/noble](https://paulmillr.com/noble/)
for useful resources, articles, documentation and demos
related to the library.
## Speed
npm run bench
Benchmarks measured with Apple M4. [noble-curves](https://github.com/paulmillr/noble-curves) enable faster performance.
```
keygen x 7,267 ops/sec @ 137μs/op
sign x 6,888 ops/sec @ 145μs/op
verify x 788 ops/sec @ 1ms/op
getSharedSecret x 654 ops/sec @ 1ms/op
recoverPublicKey x 766 ops/sec @ 1ms/op
signAsync x 4,353 ops/sec @ 229μs/op
verifyAsync x 773 ops/sec @ 1ms/op
Point.fromBytes x 13,322 ops/sec @ 75μs/op
```
## License
The MIT License (MIT)
Copyright (c) 2019 Paul Miller [(https://paulmillr.com)](https://paulmillr.com)
See LICENSE file.