npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@fungible-systems/noble-secp256k1

v1.2.9

Published

Fastest JS implementation of secp256k1. Independently audited, high-security, 0-dependency ECDSA & Schnorr signatures

Downloads

19

Readme

noble-secp256k1 Node CI code style: prettier

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 elliptic-curve cryptography in JS

This library belongs to noble crypto

noble-crypto — high-security, 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 all libraries: secp256k1, ed25519, bls12-381, ripemd160

Usage

Use NPM in node.js / browser, or include single file from GitHub's releases page:

npm install noble-secp256k1

import * as secp from "noble-secp256k1";
// if you're using single file, use global variable nobleSecp256k1 instead

(async () => {
  // You pass either a hex string, or Uint8Array
  const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e";
  const messageHash = "a33321f98e4ff1c283c76998f14f57447545d339b3db534c6d886decb4209f28";
  const publicKey = secp.getPublicKey(privateKey);
  const signature = await secp.sign(messageHash, privateKey);
  const isSigned = secp.verify(signature, messageHash, publicKey);

  // Supports Schnorr signatures
  const rpub = secp.schnorr.getPublicKey(privateKey);
  const rsignature = await secp.schnorr.sign(messageHash, privateKey);
  const risSigned = await secp.schnorr.verify(rsignature, messageHash, rpub);
})();

Deno:

import * as secp from "https://deno.land/x/secp256k1/mod.ts";
const publicKey = secp.getPublicKey("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e");

API

getPublicKey(privateKey)
function getPublicKey(privateKey: Uint8Array, isCompressed?: false): Uint8Array;
function getPublicKey(privateKey: string, isCompressed?: false): string;
function getPublicKey(privateKey: bigint): 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, publicKeyB: Uint8Array): Uint8Array;
function getSharedSecret(privateKeyA: string, publicKeyB: string): string;
function getSharedSecret(privateKeyA: bigint, publicKeyB: Point): 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).

To speed-up the function massively by precomputing EC multiplications, use getSharedSecret(privateKeyA, secp.utils.precompute(8, publicKeyB))

sign(hash, privateKey)
function sign(msgHash: Uint8Array, privateKey: Uint8Array, opts?: Options): Promise<Uint8Array>;
function sign(msgHash: string, privateKey: string, opts?: Options): Promise<string>;
function sign(msgHash: Uint8Array, privateKey: Uint8Array, opts?: Options): Promise<[Uint8Array | string, number]>;

Generates deterministic ECDSA signature as per RFC6979. Asynchronous, if you need sync version, use _syncSign().

  • 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 - determines 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 = false - determines whether a signature s should be no more than 1/2 prime order
  • Returns DER encoded ECDSA signature, as hex uint8a / string and recovered bit if options.recovered == true.
verify(signature, hash, publicKey)
function verify(signature: Uint8Array, msgHash: Uint8Array, publicKey: Uint8Array): boolean
function verify(signature: string, msgHash: string, publicKey: string): boolean
  • signature: Uint8Array | string | { r: bigint, s: bigint } - object returned by the sign function
  • msgHash: Uint8Array | string - message hash that needs to be verified
  • publicKey: Uint8Array | string | Point - e.g. that was generated from privateKey by getPublicKey
  • Returns boolean: true if signature == hash; otherwise false
recoverPublicKey(hash, signature, recovery)
export declare function recoverPublicKey(msgHash: string, signature: string, recovery: number): string | undefined;
export declare function recoverPublicKey(msgHash: Uint8Array, signature: Uint8Array, recovery: number): Uint8Array | undefined;
  • msgHash: Uint8Array | string - message hash which would be signed
  • signature: Uint8Array | string | { r: bigint, s: bigint } - object returned by the sign function
  • recovery: number - recovery bit returned by sign with recovered option 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. 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): Uint8Array;
function schnorrGetPublicKey(privateKey: string): string;

Returns 32-byte public key. Warning: it is incompatible with non-schnorr pubkey.

Specifically, its y coordinate may be flipped. See BIP0340 for clarification.

schnorr.sign(hash, privateKey)
function schnorrSign(msgHash: Uint8Array, privateKey: Uint8Array, auxilaryRandom?: Uint8Array): Promise<Uint8Array>;
function schnorrSign(msgHash: string, privateKey: string, auxilaryRandom?: string): Promise<string>;

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 random.
  • 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 the sign function
  • msgHash: Uint8Array | string - message hash that needs to be verified
  • publicKey: Uint8Array | string | Point - e.g. that was generated from privateKey by getPublicKey
  • Returns boolean: true if signature == hash; otherwise false

Point methods

Helpers
utils.randomPrivateKey(): Uint8Array

Returns Uint8Array of 32 cryptographically secure random bytes that can be used as private key.

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 speed-up 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 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 fromHex(hex: Uint8Array | string);
  toHex(): string;
}

Security

Noble is production-ready.

  1. The library has been audited by an independent security firm cure53: PDF. The audit has been crowdfunded by community with help of Umbra.cash.
  2. The library has also been fuzzed by Guido Vranken's cryptofuzz. You can run the fuzzer by yourself to check it.

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 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 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading rootkits with every npm install. Our goal is to minimize this attack vector.

Speed

Benchmarks measured with Apple M1.

getPublicKey(utils.randomPrivateKey()) x 6,121 ops/sec @ 163μs/op
sign x 4,468 ops/sec @ 223μs/op
verify x 923 ops/sec @ 1ms/op
recoverPublicKey x 491 ops/sec @ 2ms/op
getSharedSecret aka ecdh x 534 ops/sec @ 1ms/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
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

Contributing

Check out a blog post about this library: Learning fast elliptic-curve cryptography in JS.

  1. Clone the repository.
  2. npm install to install build dependencies like TypeScript
  3. npm run compile to compile TypeScript code
  4. npm run test to run jest on test/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.