Product
Introducing License Enforcement in Socket
Ensure open-source compliance with Socket’s License Enforcement Beta. Set up your License Policy and secure your software!
@noble/secp256k1
Advanced tools
Fastest JS implementation of secp256k1. Independently audited, high-security, 0-dependency ECDSA & Schnorr signatures
@noble/secp256k1 is a JavaScript library for elliptic curve cryptography, specifically for the secp256k1 curve. It is commonly used in blockchain and cryptocurrency applications for key generation, signing, and verification.
Key Generation
This feature allows you to generate a random private key and derive the corresponding public key using the secp256k1 curve.
const secp = require('@noble/secp256k1');
const privateKey = secp.utils.randomPrivateKey();
const publicKey = secp.getPublicKey(privateKey);
console.log('Private Key:', privateKey);
console.log('Public Key:', publicKey);
Signing
This feature allows you to sign a message using a private key. The message is an array of bytes, and the signature is generated using the secp256k1 curve.
const secp = require('@noble/secp256k1');
const message = new Uint8Array([1, 2, 3]);
const privateKey = secp.utils.randomPrivateKey();
const signature = secp.sign(message, privateKey);
console.log('Signature:', signature);
Verification
This feature allows you to verify a signature using the corresponding public key and the original message. It returns a boolean indicating whether the signature is valid.
const secp = require('@noble/secp256k1');
const message = new Uint8Array([1, 2, 3]);
const privateKey = secp.utils.randomPrivateKey();
const publicKey = secp.getPublicKey(privateKey);
const signature = secp.sign(message, privateKey);
const isValid = secp.verify(signature, message, publicKey);
console.log('Is the signature valid?', isValid);
Elliptic is a widely-used JavaScript library for elliptic curve cryptography. It supports multiple curves, including secp256k1. Compared to @noble/secp256k1, elliptic is more versatile but may be less optimized for performance in secp256k1-specific use cases.
Secp256k1 is a native Node.js binding to the secp256k1 library used in Bitcoin. It is highly optimized for performance but requires native compilation, which can be a drawback for some users. @noble/secp256k1, being a pure JavaScript implementation, is easier to use in browser environments.
BitcoinJS is a library for Bitcoin-related operations, including key generation, signing, and verification using secp256k1. While it offers a broader range of Bitcoin-specific functionalities, it is less focused on general elliptic curve cryptography compared to @noble/secp256k1.
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 by an independent security firm. Check out the online demo and blog post: Learning fast elliptic-curve cryptography in JS
noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.
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: `window.nobleSecp256k1`
(async () => {
// keys, messages & other inputs can be Uint8Arrays or hex strings
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef'
const privateKey = secp.utils.randomPrivateKey();
const messageHash = await secp.utils.sha256("hello world");
const publicKey = secp.getPublicKey(privateKey);
const signature = await secp.sign(messageHash, privateKey);
const isValid = secp.verify(signature, messageHash, publicKey);
// Signatures with improved security (see extraEntropy docs in README)
const signatureE = await secp.sign(messageHash, privateKey, { extraEntropy: true });
// Malleable signatures, compatible with openssl
const signatureM = await secp.sign(messageHash, privateKey, { canonical: false });
// Default output is Uint8Array. If you need hex string as an output:
console.log(secp.utils.bytesToHex(publicKey));
// Schnorr signatures
const rpub = secp.schnorr.getPublicKey(privateKey);
const rsignature = await secp.schnorr.sign(message, privateKey);
const risValid = await secp.schnorr.verify(rsignature, message, rpub);
})();
To use the module with Deno, you will need import map:
deno run --import-map=imports.json app.ts
import * as secp from "https://deno.land/x/secp256k1/mod.ts";
{"imports": {"crypto": "https://deno.land/std@0.119.0/node/crypto.ts"}}
getPublicKey(privateKey)
sign(msgHash, privateKey)
verify(signature, msgHash, publicKey)
getSharedSecret(privateKeyA, publicKeyB)
recoverPublicKey(hash, signature, recovery)
schnorr.getPublicKey(privateKey)
schnorr.sign(message, privateKey)
schnorr.verify(signature, message, publicKey)
getPublicKey(privateKey)
function getPublicKey(privateKey: Uint8Array | string | bigint, isCompressed = false): Uint8Array;
Creates public key for the corresponding private key.
isCompressed = false
determines whether to return compact (33-byte), or full (65-byte) key.Internally, it does Point.BASE.multiply(privateKey)
. If you need actual Point
instead of
Uint8Array
, use Point.fromPrivateKey(privateKey)
.
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 low-s deterministic ECDSA signature as per RFC6979.
msgHash: Uint8Array | string
- 32-byte message hash which would be signedprivateKey: Uint8Array | string | bigint
- private key which will sign the hashoptions?: Options
- optional object related to signature value and format with following keys:
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.canonical: boolean = true
- whether a signature s
should be no more than 1/2 prime order.
true
(default) makes signatures compatible with libsecp256k1,
false
makes signatures compatible with opensslder: boolean = true
- whether the returned signature should be in DER format. If false
, it would be in Compact format (32-byte r + 32-byte s)extraEntropy: Uint8Array | string | true
- additional entropy k'
for deterministic signature, follows section 3.6 of RFC6979. When true
, it would automatically be filled with 32 bytes of cryptographically secure entropy. Strongly recommended to pass true
to improve security:
k
generation. Exposing k
could leak private keysr
/ s
, which means they
would still be valid, but may break some test vectors if you're cross-testing against other libsThe function is asynchronous because we're utilizing built-in HMAC API to not rely on dependencies.
function signSync(msgHash: Uint8Array | string, privateKey: Uint8Array | string, opts?: Options): Uint8Array | [Uint8Array, number];
signSync
counterpart could also be used, you need to set utils.hmacSha256Sync
to a function with signature key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
. Example with noble-hashes
package:
import { hmac } = from '@noble/hashes/hmac';
import { sha256 } from '@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 the sign
functionmsgHash: Uint8Array | string
- message hash that needs to be verifiedpublicKey: Uint8Array | string | Point
- e.g. that was generated from privateKey
by getPublicKey
options?: Options
- optional object related to signature value and format
strict: boolean = true
- whether a signature s
should be no more than 1/2 prime order.
true
(default) makes signatures compatible with libsecp256k1,
false
makes signatures compatible with opensslboolean
: true
if signature == hash
; otherwise false
getSharedSecret(privateKeyA, publicKeyB)
function getSharedSecret(privateKeyA: Uint8Array | string | bigint, publicKeyB: Uint8Array | string | Point, isCompressed = false): Uint8Array;
Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between a private key and a different public key.
To get Point instance, use Point.fromHex(publicKeyB).multiply(privateKeyA)
isCompressed = false
determines whether to return compact (33-byte), or full (65-byte) key
If you have one public key you'll be creating lots of secrets against, consider massive speed-up by using precomputations:
const pub = secp.utils.precompute(8, publicKeyB);
// Use pub everywhere instead of publicKeyB
getSharedSecret(privKey, pub); // Now 12x faster
recoverPublicKey(hash, signature, recovery)
function recoverPublicKey(msgHash: Uint8Array | string, signature: Uint8Array | string, recovery: number, isCompressed = false): Uint8Array | undefined;
msgHash: Uint8Array | string
- message hash which would be signedsignature: Uint8Array | string | { r: bigint, s: bigint }
- object returned by the sign
functionrecovery: number
- recovery bit returned by sign
with recovered
optionisCompressed = false
determines whether to return compact (33-byte), or full (65-byte) keyPublic 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.
If signature is invalid - function will return undefined
as result.
To get Point instance, use Point.fromSignature(hash, signature, recovery)
.
schnorr.getPublicKey(privateKey)
function schnorrGetPublicKey(privateKey: Uint8Array | string): Uint8Array;
Calculates 32-byte public key from a private key.
Warning: it is incompatible with non-schnorr pubkey. Specifically, its y coordinate may be flipped. See BIP340 for clarification.
schnorr.sign(message, privateKey)
function schnorrSign(message: Uint8Array | string, privateKey: Uint8Array | string, auxilaryRandom?: Uint8Array): Promise<Uint8Array>;
Generates Schnorr signature as per BIP0340. Asynchronous, so use await
.
message: Uint8Array | string
- message (not hash) which would be signedprivateKey: Uint8Array | string | bigint
- private key which will sign the hashauxilaryRandom?: Uint8Array
— optional 32 random bytes. By default, the method gathers cryptogarphically secure entropyschnorr.verify(signature, message, publicKey)
function schnorrVerify(signature: Uint8Array | string, message: Uint8Array | string, publicKey: Uint8Array | string): boolean
signature: Uint8Array | string | { r: bigint, s: bigint }
- object returned by the sign
functionmessage: Uint8Array | string
- message (not hash) that needs to be verifiedpublicKey: Uint8Array | string | Point
- e.g. that was generated from privateKey
by getPublicKey
boolean
: true
if signature == hash
; otherwise false
secp256k1 exposes a few internal utilities for improved developer experience:
const utils: {
// Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF
// and convert them into private key, with the modulo bias being neglible.
// As per FIPS 186 B.1.1.
hashToPrivateKey: (hash: Hex) => Uint8Array;
// Returns `Uint8Array` of 32 cryptographically secure random bytes that can be used as private key
randomPrivateKey: () => Uint8Array;
// Checks private key for validity
isValidPrivateKey(privateKey: PrivKey): boolean;
// Returns `Uint8Array` of x cryptographically secure random bytes.
randomBytes: (bytesLength?: number) => Uint8Array;
// Converts Uint8Array to hex string
bytesToHex: typeof bytesToHex;
// Modular division over curve prime
mod: (number: number | bigint, modulo = CURVE.P): bigint;
sha256: (message: Uint8Array) => Promise<Uint8Array>;
hmacSha256: (key: Uint8Array, ...messages: Uint8Array[]) => Promise<Uint8Array>;
// You can set up your synchronous methods for `signSync` to work. The argument order is
// identical to async methods from above
sha256Sync: undefined;
hmacSha256Sync: undefined;
// 1. Returns cached point which you can use to pass to `getSharedSecret` or to `#multiply` by it.
// 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call.
// If you want your first getPublicKey to take 0.16ms instead of 20ms, make sure to call
// utils.precompute() somewhere without arguments first.
precompute(windowSize?: number, point?: Point): 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 non-compressed 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;
// Constant-time 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 32-byte each
static fromCompact(hex: Uint8Array | string);
assertValidity(): void;
hasHighS(): boolean; // high-S sigs cannot be produced using { canonical: true }
toDERRawBytes(): Uint8Array;
toDERHex(): string;
toCompactRawBytes(): Uint8Array;
toCompactHex(): string;
}
Noble is production-ready.
We're using built-in 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, JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language. Which means any other JS library doesn't use constant-time bigints. Including bn.js or anything else. 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. Nonetheless we've hardened implementation of ec 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 3rd-party 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.
Benchmarks measured with Apple M1 on MacOS 12.
getPublicKey(utils.randomPrivateKey()) x 6,300 ops/sec @ 158μs/op
sign x 4,888 ops/sec @ 204μs/op
verify x 950 ops/sec @ 1ms/op
recoverPublicKey x 860 ops/sec @ 1ms/op
getSharedSecret aka ecdh x 576 ops/sec @ 1ms/op
getSharedSecret (precomputed) x 6,688 ops/sec @ 149μs/op
Point.fromHex (decompression) x 12,553 ops/sec @ 79μs/op
schnorr.sign x 687 ops/sec @ 1ms/op
schnorr.verify x 990 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
bip-schnorr#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
bip-schnorr#verify x 56 ops/sec
elliptic#ecdh x 971 ops/sec
Check out a blog post about this library: Learning fast elliptic-curve cryptography in JS.
npm install
to install build dependencies like TypeScriptnpm run build
to compile TypeScript codenpm test
to run jest on test/index.ts
Special thanks to Roman Koblov, who have helped to improve scalar multiplication speed.
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.
FAQs
Fastest 4KB JS implementation of secp256k1 ECDH & ECDSA signatures compliant with RFC6979
The npm package @noble/secp256k1 receives a total of 404,802 weekly downloads. As such, @noble/secp256k1 popularity was classified as popular.
We found that @noble/secp256k1 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Ensure open-source compliance with Socket’s License Enforcement Beta. Set up your License Policy and secure your software!
Product
We're launching a new set of license analysis and compliance features for analyzing, managing, and complying with licenses across a range of supported languages and ecosystems.
Product
We're excited to introduce Socket Optimize, a powerful CLI command to secure open source dependencies with tested, optimized package overrides.