noblesecp256k1
Fastest JS implementation of secp256k1, an elliptic curve that could be used for asymmetric encryption, ECDH key agreement protocol and signature schemes. Supports deterministic ECDSA from RFC6979 and Schnorr signatures from BIP0340.
Audited with crowdfunding by an independent security firm. Tested against thousands of test vectors from a different library. Check out the online demo and blog post: Learning fast ellipticcurve cryptography in JS
This library belongs to noble crypto
noblecrypto — highsecurity, easily auditable set of contained cryptographic libraries and tools.
 No dependencies, one small file
 Easily auditable TypeScript/JS code
 Supported in all major browsers and stable node.js versions
 All releases are signed with PGP keys
 Check out homepage & all libraries: secp256k1, ed25519, bls12381, hashes
Usage
Use NPM in node.js / browser, or include single file from GitHub's releases page:
npm install @noble/secp256k1
// Common.js and ECMAScript Modules (ESM)
import * as secp from "@noble/secp256k1";
// If you're using single file, use global variable instead:
// nobleSecp256k1
(async () => {
// You pass a hex string, or Uint8Array
const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e";
const message = "hello world";
const messageHash = await secp.utils.sha256(message);
const publicKey = secp.getPublicKey(privateKey);
const signature = await secp.sign(messageHash, privateKey);
const isSigned = secp.verify(signature, messageHash, publicKey);
// Sigs with improved security (see README)
const signatureE = await secp.sign(messageHash, privateKey, { extraEntropy: true });
// Malleable signatures, compatible with openssl
const signatureM = await secp.sign(messageHash, privateKey, { canonical: false });
// Supports Schnorr signatures
const rpub = secp.schnorr.getPublicKey(privateKey);
const rsignature = await secp.schnorr.sign(message, privateKey);
const risSigned = await secp.schnorr.verify(rsignature, message, rpub);
})();
To use the module with Deno, you will need import map:

deno run importmap=imports.json app.ts

app.ts
import * as secp from "https://deno.land/x/secp256k1/mod.ts"; const publicKey = secp.getPublicKey(secp.utils.randomPrivateKey()); console.log(publicKey);

imports.json
{ "imports": { "crypto": "https://deno.land/std@0.119.0/node/crypto.ts" } }
API
getPublicKey(privateKey)
getSharedSecret(privateKeyA, publicKeyB)
sign(msgHash, privateKey)
verify(signature, msgHash, publicKey)
recoverPublicKey(hash, signature, recovery)
schnorr.getPublicKey(privateKey)
schnorr.sign(hash, privateKey)
schnorr.verify(signature, hash, publicKey)
 Helpers
getPublicKey(privateKey)
function getPublicKey(privateKey: Uint8Array  string  bigint, isCompressed = false): Uint8Array;
privateKey
will be used to generate public key.
Public key is generated by doing scalar multiplication of a base Point(x, y) by a fixed
integer. The result is another Point(x, y)
which we will by default encode to hex Uint8Array.
isCompressed
(default is false
) determines whether the output should contain y
coordinate of the point.
To get Point instance, use Point.fromPrivateKey(privateKey)
.
getSharedSecret(privateKeyA, publicKeyB)
function getSharedSecret(privateKeyA: Uint8Array  string  bigint, publicKeyB: Uint8Array  string  Point): Uint8Array;
Computes ECDH (Elliptic Curve DiffieHellman) shared secret between a private key and a different public key.
To get Point instance, use Point.fromHex(publicKeyB).multiply(privateKeyA)
.
To speedup the function massively by precomputing EC multiplications,
use getSharedSecret(privateKeyA, secp.utils.precompute(8, publicKeyB))
sign(msgHash, privateKey)
function sign(msgHash: Uint8Array  string, privateKey: Uint8Array  string, opts?: Options): Promise<Uint8Array>;
function sign(msgHash: Uint8Array  string, privateKey: Uint8Array  string, opts?: Options): Promise<[Uint8Array, number]>;
Generates lows deterministic ECDSA signature as per RFC6979.
It's strongly recommended to pass {extraEntropy: true}
to improve security of signatures:
 In case the entropy generator is broken, signatures would be just like they are without the option
 It would help a lot in case there is an error somewhere in
k
generation. Exposingk
could leak private keys  Schnorr signatures are adding extra entropy every time
 The only disadvantage to this is the fact signatures won't be exactly equal to fullydeterministic sigs; think backwardscompatibility with test vectors. They would still be valid, though
sign
arguments:

msgHash: Uint8Array  string
 message hash which would be signed 
privateKey: Uint8Array  string  bigint
 private key which will sign the hash 
options?: Options
 optional object related to signature value and format 
options?.recovered: boolean = false
 whether the recovered bit should be included in the result. In this case, the result would be an array of two items. 
options?.canonical: boolean = true
 whether a signatures
should be no more than 1/2 prime order.true
makes signatures compatible with libsecp256k1,false
makes signatures compatible with openssl 
options?.extraEntropy: Uint8Array  string  true
 additional entropyk'
for deterministic signature, follows section 3.6 of RFC6979. Whentrue
, it would automatically be filled with 32 bytes of cryptographically secure entropy 
options?.der: boolean = true
 whether the returned signature should be in DER format. Iffalse
, it would be in Compact format (32byte r + 32byte s)
The function is asynchronous because we're utilizing builtin HMAC API to not rely on dependencies.
signSync
counterpart could also be used, you need to set utils.hmacSha256Sync
to a function with signature key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
. Example with noblehashes
package:
const { hmac } = require('@noble/hashes/hmac');
const { sha256 } = require('@noble/hashes/sha256');
secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => {
const h = hmac.create(sha256, key);
msgs.forEach(msg => h.update(msg));
return h.digest();
};
// Can be used now
secp256k1.signSync(msgHash, privateKey)
verify(signature, msgHash, publicKey)
function verify(signature: Uint8Array  string, msgHash: Uint8Array  string, publicKey: Uint8Array  string): boolean
function verify(signature: Signature, msgHash: Uint8Array  string, publicKey: Point): boolean

signature: Uint8Array  string  { r: bigint, s: bigint }
 object returned by thesign
function 
msgHash: Uint8Array  string
 message hash that needs to be verified 
publicKey: Uint8Array  string  Point
 e.g. that was generated fromprivateKey
bygetPublicKey

options?: Options
 optional object related to signature value and format 
options?.strict: boolean = true
 whether a signatures
should be no more than 1/2 prime order.true
makes signatures compatible with libsecp256k1,false
makes signatures compatible with openssl  Returns
boolean
:true
ifsignature == hash
; otherwisefalse
recoverPublicKey(hash, signature, recovery)
function recoverPublicKey(msgHash: Uint8Array  string, signature: Uint8Array  string, recovery: number): Uint8Array  undefined;

msgHash: Uint8Array  string
 message hash which would be signed 
signature: Uint8Array  string  { r: bigint, s: bigint }
 object returned by thesign
function 
recovery: number
 recovery bit returned bysign
withrecovered
option Public key is generated by doing scalar multiplication of a base Point(x, y) by a fixed integer. The result is anotherPoint(x, y)
which we will by default encode to hex Uint8Array. If signature is invalid  function will returnundefined
as result.
To get Point instance, use Point.fromSignature(hash, signature, recovery)
.
schnorr.getPublicKey(privateKey)
function schnorrGetPublicKey(privateKey: Uint8Array  string): Uint8Array;
Returns 32byte public key. Warning: it is incompatible with nonschnorr pubkey.
Specifically, its y coordinate may be flipped. See BIP340 for clarification.
schnorr.sign(hash, privateKey)
function schnorrSign(msgHash: Uint8Array  string, privateKey: Uint8Array  string, auxilaryRandom?: Uint8Array): Promise<Uint8Array>;
Generates Schnorr signature as per BIP0340. Asynchronous, so use await
.

msgHash: Uint8Array  string
 message hash which would be signed 
privateKey: Uint8Array  string  bigint
 private key which will sign the hash 
auxilaryRandom?: Uint8Array
— optional 32 random bytes. By default, the method gathers cryptogarphically secure entropy  Returns Schnorr signature in Hex format.
schnorr.verify(signature, hash, publicKey)
function schnorrVerify(signature: Uint8Array  string, msgHash: Uint8Array  string, publicKey: Uint8Array  string): boolean

signature: Uint8Array  string  { r: bigint, s: bigint }
 object returned by thesign
function 
msgHash: Uint8Array  string
 message hash that needs to be verified 
publicKey: Uint8Array  string  Point
 e.g. that was generated fromprivateKey
bygetPublicKey
 Returns
boolean
:true
ifsignature == hash
; otherwisefalse
Point methods
Helpers
utils.randomBytes(): Uint8Array
Returns Uint8Array
of 32 cryptographically secure random bytes.
Uses crypto.web.getRandomValues
in browser, require('crypto').randomBytes
in node.js.
utils.randomPrivateKey(): Uint8Array
Returns Uint8Array
of 32 cryptographically secure random bytes that can be used as private key. The signature is:
(key: Uint8Array, ...msgs: Uint8Array[]): Uint8Array;
utils.bytesToHex(bytes: Uint8Array): string
Converts a byte array to hex string.
utils.sha256
and utils.hmacSha256
Asynchronous methods that calculate SHA256
and HMACSHA256
. Use browser builtins by default.
utils.sha256Sync
and utils.hmacSha256Sync
The functions are not defined by default, but could be used to implement signSync
method (see above).
utils.precompute(W = 8, point = BASE_POINT): Point
Returns cached point which you can use to pass to getSharedSecret
or to #multiply
by it.
This is done by default, no need to run it unless you want to disable precomputation or change window size.
We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT values.
This slows down first getPublicKey() by milliseconds (see Speed section), but allows to speedup subsequent getPublicKey() calls up to 20x.
You may want to precompute values for your own point.
secp256k1.CURVE.P // Field, 2 ** 256  2 ** 32  977
secp256k1.CURVE.n // Order, 2 ** 256  432420386565659656852420866394968145599
secp256k1.Point.BASE // new secp256k1.Point(Gx, Gy) where
// Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240n
// Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
// Elliptic curve point in Affine (x, y) coordinates.
secp256k1.Point {
constructor(x: bigint, y: bigint);
// Supports compressed and noncompressed hex
static fromHex(hex: Uint8Array  string);
static fromPrivateKey(privateKey: Uint8Array  string  number  bigint);
static fromSignature(
msgHash: Hex,
signature: Signature,
recovery: number  bigint
): Point  undefined {
toRawBytes(isCompressed = false): Uint8Array;
toHex(isCompressed = false): string;
equals(other: Point): boolean;
negate(): Point;
add(other: Point): Point;
subtract(other: Point): Point;
// Constanttime scalar multiplication.
multiply(scalar: bigint  Uint8Array): Point;
}
secp256k1.Signature {
constructor(r: bigint, s: bigint);
// DER encoded ECDSA signature
static fromDER(hex: Uint8Array  string);
// R, S 32byte each
static fromCompact(hex: Uint8Array  string);
assertValidity(): void;
hasHighS(): boolean; // highS sigs cannot be produced using { canonical: true }
toDERRawBytes(): Uint8Array;
toDERHex(): string;
toCompactRawBytes(): Uint8Array;
toCompactHex(): string;
}
Security
Noble is productionready.
 The library has been audited by an independent security firm cure53: PDF. The audit has been crowdfunded by community with help of Umbra.cash.
 The library has also been fuzzed by Guido Vranken's cryptofuzz. You can run the fuzzer by yourself to check it.
We're using builtin JS BigInt
, which is "unsuitable for use in cryptography" as per official spec. This means that the lib is potentially vulnerable to timing attacks. But, JITcompiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language. Which means any other JS library doesn't use constanttime bigints. Including bn.js or anything else. Even statically typed Rust, a language without GC, makes it harder to achieve constanttime for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use lowlevel libraries & languages. Nonetheless we've hardened implementation of koblitz curve multiplication to be algorithmically constant time.
We however consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rdparty dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every npm install
. Our goal is to minimize this attack vector.
Speed
Benchmarks measured with Apple M1 on MacOS 12.
getPublicKey(utils.randomPrivateKey()) x 6,216 ops/sec @ 160μs/op
sign x 4,789 ops/sec @ 208μs/op
verify x 923 ops/sec @ 1ms/op
recoverPublicKey x 491 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 558 ops/sec @ 1790μs/op
getSharedSecret (precomputed) x 7,105 ops/sec @ 140μs/op
Point.fromHex (decompression) x 12,171 ops/sec @ 82μs/op
schnorr.sign x 409 ops/sec @ 2ms/op
schnorr.verify x 504 ops/sec @ 1ms/op
Compare to other libraries (openssl
uses native bindings, not JS):
elliptic#getPublicKey x 1,940 ops/sec
sjcl#getPublicKey x 211 ops/sec
elliptic#sign x 1,808 ops/sec
sjcl#sign x 199 ops/sec
openssl#sign x 4,243 ops/sec
ecdsa#sign x 116 ops/sec
bipschnorr#sign x 60 ops/sec
elliptic#verify x 812 ops/sec
sjcl#verify x 166 ops/sec
openssl#verify x 4,452 ops/sec
ecdsa#verify x 80 ops/sec
bipschnorr#verify x 56 ops/sec
elliptic#ecdh x 971 ops/sec
Contributing
Check out a blog post about this library: Learning fast ellipticcurve cryptography in JS.
 Clone the repository.

npm install
to install build dependencies like TypeScript 
npm run compile
to compile TypeScript code 
npm run test
to run jest ontest/index.ts
Special thanks to Roman Koblov, who have helped to improve scalar multiplication speed.
License
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.