@noble/secp256k1
Advanced tools
Comparing version 1.7.1 to 2.0.0
{ | ||
"name": "@noble/secp256k1", | ||
"version": "1.7.1", | ||
"description": "Fastest JS implementation of secp256k1. Independently audited, high-security, 0-dependency ECDSA & Schnorr signatures", | ||
"version": "2.0.0", | ||
"description": "Fastest 4KB JS implementation of secp256k1 elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979", | ||
"files": [ | ||
"lib" | ||
"index.js", | ||
"index.d.ts", | ||
"index.ts" | ||
], | ||
"main": "lib/index.js", | ||
"module": "lib/esm/index.js", | ||
"types": "lib/index.d.ts", | ||
"type": "module", | ||
"main": "index.js", | ||
"module": "index.js", | ||
"types": "index.d.ts", | ||
"scripts": { | ||
"build": "tsc && tsc -p tsconfig.esm.json", | ||
"build": "tsc", | ||
"build:release": "rollup -c rollup.config.js", | ||
"lint": "prettier --print-width 100 --single-quote --check index.ts", | ||
"format": "prettier --print-width 100 --single-quote --write index.ts", | ||
"test": "jest", | ||
"coverage": "jest --coverage", | ||
"bench": "node test/benchmark.js" | ||
"test": "node test/secp256k1.test.mjs", | ||
"bench": "node test/benchmark.js", | ||
"min": "cd test/build; npm install; npm run terser", | ||
"loc": "echo \"`npm run --silent min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent min | gzip -c8 | wc -c`B gzipped\"" | ||
}, | ||
@@ -27,52 +29,29 @@ "author": "Paul Miller (https://paulmillr.com)", | ||
"license": "MIT", | ||
"browser": { | ||
"crypto": false | ||
}, | ||
"devDependencies": { | ||
"@noble/hashes": "1.1.2", | ||
"@rollup/plugin-commonjs": "22.0.0", | ||
"@rollup/plugin-node-resolve": "13.3.0", | ||
"@types/jest": "28.1.1", | ||
"@types/node": "17.0.18", | ||
"@noble/hashes": "1.3.0", | ||
"fast-check": "3.0.0", | ||
"jest": "28.1.0", | ||
"micro-bmark": "0.2.0", | ||
"prettier": "2.6.2", | ||
"rollup": "2.75.5", | ||
"ts-jest": "28.0.4", | ||
"typescript": "4.7.3" | ||
"micro-bmark": "0.3.0", | ||
"micro-should": "0.4.0", | ||
"typescript": "5.0.2" | ||
}, | ||
"keywords": [ | ||
"secp256k1", | ||
"secp", | ||
"secp256", | ||
"elliptic", | ||
"rfc6979", | ||
"signature", | ||
"ecdsa", | ||
"noble", | ||
"cryptography", | ||
"elliptic curve", | ||
"ecc", | ||
"curve", | ||
"signature", | ||
"ecc", | ||
"rfc6979", | ||
"schnorr", | ||
"sig", | ||
"bip0340", | ||
"bip340", | ||
"ecdsa", | ||
"endomorphism", | ||
"cryptography", | ||
"security", | ||
"noble" | ||
"bitcoin", | ||
"ethereum" | ||
], | ||
"exports": { | ||
".": { | ||
"types": "./lib/index.d.ts", | ||
"import": "./lib/esm/index.js", | ||
"default": "./lib/index.js" | ||
"types": "./index.d.ts", | ||
"default": "./index.js" | ||
} | ||
}, | ||
"jest": { | ||
"testRegex": "/test/.*?\\.ts", | ||
"transform": { | ||
"^.+\\.ts$": "ts-jest" | ||
} | ||
}, | ||
"funding": [ | ||
@@ -79,0 +58,0 @@ { |
533
README.md
@@ -1,9 +0,15 @@ | ||
# noble-secp256k1 ![Node CI](https://github.com/paulmillr/noble-secp256k1/workflows/Node%20CI/badge.svg) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) | ||
# noble-secp256k1 | ||
[Fastest](#speed) JS implementation of [secp256k1](https://www.secg.org/sec2-v2.pdf), | ||
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](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). | ||
[Fastest](#speed) 4KB JS implementation of [secp256k1](https://www.secg.org/sec2-v2.pdf) | ||
elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979. | ||
[**Audited**](#security) by an independent security firm. Check out [the online demo](https://paulmillr.com/ecc) and blog post: [Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/) | ||
The library is a tiny single-feature version of | ||
[noble-curves](https://github.com/paulmillr/noble-curves), with some features | ||
removed. Check out curves as a drop-in replacement with | ||
Schnorr signatures, DER encoding and support for different hash functions. | ||
Take a look at: [Upgrading](#upgrading) section for v1 to v2 transition instructions, | ||
[the online demo](https://paulmillr.com/noble/) and blog post | ||
[Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). | ||
### This library belongs to _noble_ crypto | ||
@@ -13,10 +19,10 @@ | ||
- No dependencies, one small file | ||
- Easily auditable TypeScript/JS code | ||
- No dependencies, protection against supply chain attacks | ||
- Auditable TypeScript / JS code | ||
- Supported in all major browsers and stable node.js versions | ||
- All releases are signed with PGP keys | ||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries: | ||
[secp256k1](https://github.com/paulmillr/noble-secp256k1), | ||
[ed25519](https://github.com/paulmillr/noble-ed25519), | ||
[bls12-381](https://github.com/paulmillr/noble-bls12-381), | ||
[curves](https://github.com/paulmillr/noble-curves) | ||
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1), | ||
[ed25519](https://github.com/paulmillr/noble-ed25519)), | ||
[hashes](https://github.com/paulmillr/noble-hashes) | ||
@@ -26,4 +32,3 @@ | ||
Use NPM in node.js / browser, or include single file from | ||
[GitHub's releases page](https://github.com/paulmillr/noble-secp256k1/releases): | ||
Browser, deno, node.js and unpkg are supported: | ||
@@ -33,315 +38,169 @@ > npm install @noble/secp256k1 | ||
```js | ||
// Common.js and ECMAScript Modules (ESM) | ||
import * as secp from '@noble/secp256k1'; | ||
// If you're using single file, use global variable instead: `window.nobleSecp256k1` | ||
// Supports both async and sync methods, see docs | ||
import * as secp from '@noble/secp256k1'; // ESM-only. Use bundler for common.js | ||
// import * as secp from "https://deno.land/x/secp256k1/mod.ts"; // Deno | ||
// import * as secp from "https://unpkg.com/@noble/secp256k1"; // Unpkg | ||
(async () => { | ||
// keys, messages & other inputs can be Uint8Arrays or hex strings | ||
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef' | ||
const privKey = secp.utils.randomPrivateKey(); | ||
const pubKey = secp.getPublicKey(privKey); | ||
const msgHash = await secp.utils.sha256('hello world'); | ||
const signature = await secp.sign(msgHash, privKey); | ||
const isValid = secp.verify(signature, msgHash, pubKey); | ||
const privKey = secp.utils.randomPrivateKey(); // Secure random private key | ||
// sha256 of 'hello world' | ||
const msgHash = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; | ||
const pubKey = secp.getPublicKey(privKey); // Make pubkey from the private key | ||
const signature = await secp.signAsync(msgHash, privKey); // sign | ||
const isValid = secp.verify(signature, msgHash, pubKey); // verify | ||
// Schnorr signatures | ||
const rpub = secp.schnorr.getPublicKey(privKey); | ||
const rsignature = await secp.schnorr.sign(message, privKey); | ||
const risValid = await secp.schnorr.verify(rsignature, message, rpub); | ||
const pubKey2 = getPublicKey(secp.utils.randomPrivateKey()); // Key of user 2 | ||
secp.getSharedSecret(privKey, alicesPubkey); // Elliptic curve diffie-hellman | ||
signature.recoverPublicKey(msgHash); // Public key recovery | ||
})(); | ||
``` | ||
To use the module with [Deno](https://deno.land), | ||
you will need [import map](https://deno.land/manual/linking_to_external_code/import_maps): | ||
Advanced examples: | ||
- `deno run --import-map=imports.json app.ts` | ||
- app.ts: `import * as secp from "https://deno.land/x/secp256k1/mod.ts";` | ||
- imports.json: `{"imports": {"crypto": "https://deno.land/std@0.153.0/node/crypto.ts"}}` | ||
```ts | ||
// 1. Use the shim to enable synchronous methods. | ||
// Only async methods are available by default to keep library dependency-free. | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) | ||
const signature2 = secp.sign(msgHash, privKey); // Can be used now | ||
## API | ||
// 2. Use the shim only for node.js <= 18 BEFORE importing noble-secp256k1. | ||
// The library depends on global variable crypto to work. It is available in | ||
// all browsers and many environments, but node.js <= 18 don't have it. | ||
import { webcrypto } from 'node:crypto'; | ||
// @ts-ignore | ||
if (!globalThis.crypto) globalThis.crypto = webcrypto; | ||
- [`getPublicKey(privateKey)`](#getpublickeyprivatekey) | ||
- [`sign(msgHash, privateKey)`](#signmsghash-privatekey) | ||
- [`verify(signature, msgHash, publicKey)`](#verifysignature-msghash-publickey) | ||
- [`getSharedSecret(privateKeyA, publicKeyB)`](#getsharedsecretprivatekeya-publickeyb) | ||
- [`recoverPublicKey(hash, signature, recovery)`](#recoverpublickeyhash-signature-recovery) | ||
- [`schnorr.getPublicKey(privateKey)`](#schnorrgetpublickeyprivatekey) | ||
- [`schnorr.sign(message, privateKey)`](#schnorrsignmessage-privatekey) | ||
- [`schnorr.verify(signature, message, publicKey)`](#schnorrverifysignature-message-publickey) | ||
- [Utilities](#utilities) | ||
// Other stuff | ||
// Malleable signatures, incompatible with BTC/ETH, but compatible with openssl | ||
// `lowS: true` prohibits signatures which have (sig.s >= CURVE.n/2n) because of | ||
// malleability | ||
const signatureMalleable = secp.sign(msgHash, privKey, { lowS: false }); | ||
##### `getPublicKey(privateKey)` | ||
```typescript | ||
function getPublicKey(privateKey: Uint8Array | string | bigint, isCompressed = false): Uint8Array; | ||
// Signatures with improved security: adds additional entropy `k` for | ||
// deterministic signature, follows section 3.6 of RFC6979. When `true`, it | ||
// would be filled with 32b from CSPRNG. **Strongly recommended** to pass `true` | ||
// to improve security: | ||
// - No disadvantage: if an entropy generator is broken, sigs would be the same | ||
// as they are without the option | ||
// - It would help a lot in case there is an error somewhere in `k` gen. | ||
// Exposing `k` could leak private keys | ||
// - Sigs with extra entropy would have different `r` / `s`, which means they | ||
// would still be valid, but may break some test vectors if you're | ||
// cross-testing against other libs | ||
const signatureImproved = secp.sign(msgHash, privKey, { extraEntropy: true }); | ||
``` | ||
Creates public key for the corresponding private key. The default is full 65-byte key. | ||
## API | ||
- `isCompressed = false` determines whether to return compact (33-byte), or full (65-byte) key. | ||
There are 3 main methods: `getPublicKey(privateKey)`, | ||
`sign(messageHash, privateKey)` and | ||
`verify(signature, messageHash, publicKey)`. | ||
Internally, it does `Point.BASE.multiply(privateKey)`. If you need actual `Point` instead of | ||
`Uint8Array`, use `Point.fromPrivateKey(privateKey)`. | ||
```typescript | ||
type Hex = Uint8Array | string; | ||
##### `sign(msgHash, privateKey)` | ||
// Generates public key from 32-byte private key. | ||
// isCompressed=true by default, meaning 33-byte output. Set to false for 65b. | ||
function getPublicKey(privateKey: Hex, isCompressed?: boolean): Uint8Array; | ||
// Use: | ||
// - `ProjectivePoint.fromPrivateKey(privateKey)` for Point instance | ||
// - `ProjectivePoint.fromHex(publicKey)` to convert hex / bytes into Point. | ||
```typescript | ||
// Generates low-s deterministic-k RFC6979 ECDSA signature. | ||
// Use with `extraEntropy: true` to improve security. | ||
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]>; | ||
``` | ||
messageHash: Hex, // message hash (not message) which would be signed | ||
privateKey: Hex, // private key which will sign the hash | ||
opts?: { lowS: boolean, extraEntropy: boolean | Hex } // optional params | ||
): Signature; | ||
function signAsync( | ||
messageHash: Hex, | ||
privateKey: Hex, | ||
opts?: { lowS: boolean; extraEntropy: boolean | Hex } | ||
): Promise<Signature>; | ||
Generates low-s deterministic ECDSA signature as per RFC6979. | ||
- `msgHash: Uint8Array | string` - 32-byte 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 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 openssl | ||
- `der: 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: | ||
- Schnorr signatures are doing it every time | ||
- It would help a lot in case there is an error somewhere in `k` generation. Exposing `k` could leak private keys | ||
- If the entropy generator is broken, signatures would be the same as they are without the option | ||
- Signatures with extra entropy would have different `r` / `s`, which means they | ||
would still be valid, but may break some test vectors if you're cross-testing against other libs | ||
The function is asynchronous because we're utilizing built-in HMAC API to not rely on dependencies. | ||
```ts | ||
(async () => { | ||
// Signatures with improved security | ||
const signatureE = await secp.sign(msgHash, privKey, { extraEntropy: true }); | ||
// Malleable signatures, but compatible with openssl | ||
const signatureM = await secp.sign(msgHash, privKey, { canonical: false }); | ||
})(); | ||
``` | ||
```typescript | ||
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: | ||
```ts | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp256k1.utils.hmacSha256Sync = (key, ...msgs) => hmac(sha256, key, secp256k1.utils.concatBytes(...msgs)) | ||
secp256k1.utils.sha256Sync = (...msgs) => sha256(secp256k1.utils.concatBytes(...msgs)) | ||
// Can be used now | ||
secp256k1.signSync(msgHash, privateKey); | ||
schnorr.signSync(message, privateKey) | ||
``` | ||
##### `verify(signature, msgHash, publicKey)` | ||
```typescript | ||
// Verifies ECDSA signature. | ||
// lowS option Ensures a signature.s is in the lower-half of CURVE.n. | ||
// Used in BTC, ETH. | ||
// `{ lowS: false }` should only be used if you need OpenSSL-compatible signatures | ||
function verify( | ||
signature: Uint8Array | string, | ||
msgHash: Uint8Array | string, | ||
publicKey: Uint8Array | string | ||
signature: Hex | Signature, // returned by the `sign` function | ||
messageHash: Hex, // message hash (not message) that must be verified | ||
publicKey: Hex, // public (not private) key | ||
opts?: { lowS: boolean } // optional params; { lowS: true } by default | ||
): boolean; | ||
function verify(signature: Signature, msgHash: Uint8Array | string, publicKey: Point): 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` | ||
- `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 openssl | ||
- Returns `boolean`: `true` if `signature == hash`; otherwise `false` | ||
##### `getSharedSecret(privateKeyA, publicKeyB)` | ||
```typescript | ||
// Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between | ||
// key A and different key B. | ||
function getSharedSecret( | ||
privateKeyA: Uint8Array | string | bigint, | ||
publicKeyB: Uint8Array | string | Point, | ||
isCompressed = false | ||
privateKeyA: Uint8Array | string, // Alices's private key | ||
publicKeyB: Uint8Array | string, // Bob's public key | ||
isCompressed = true // optional arg. (default) true=33b key, false=65b. | ||
): Uint8Array; | ||
``` | ||
// Use `ProjectivePoint.fromHex(publicKeyB).multiply(privateKeyA)` for Point instance | ||
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: | ||
```js | ||
const pub = secp.utils.precompute(8, publicKeyB); | ||
// Use pub everywhere instead of publicKeyB | ||
getSharedSecret(privKey, pub); // Now 12x faster | ||
``` | ||
##### `recoverPublicKey(hash, signature, recovery)` | ||
```typescript | ||
function recoverPublicKey( | ||
msgHash: Uint8Array | string, | ||
signature: Uint8Array | string, | ||
recovery: number, | ||
isCompressed = false | ||
// Recover public key from Signature instance with `recovery` bit set | ||
signature.recoverPublicKey( | ||
msgHash: Uint8Array | string | ||
): Uint8Array | undefined; | ||
``` | ||
Recovers public key from message hash, signature & recovery bit. The default is full 65-byte key. | ||
A bunch of useful **utilities** are also exposed: | ||
- `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 | ||
- `isCompressed = false` determines whether to return compact (33-byte), or full (65-byte) 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. | ||
If signature is invalid - function will return `undefined` as result. | ||
To get Point instance, use `Point.fromSignature(hash, signature, recovery)`. | ||
##### `schnorr.getPublicKey(privateKey)` | ||
```typescript | ||
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](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) for clarification. | ||
##### `schnorr.sign(message, privateKey)` | ||
```typescript | ||
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 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, message, publicKey)` | ||
```typescript | ||
function schnorrVerify( | ||
signature: Uint8Array | string, | ||
message: Uint8Array | string, | ||
publicKey: Uint8Array | string | ||
): boolean; | ||
``` | ||
- `signature: Uint8Array | string | { r: bigint, s: bigint }` - object returned by the `sign` function | ||
- `message: Uint8Array | string` - message (not 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` | ||
#### Utilities | ||
secp256k1 exposes a few internal utilities for improved developer experience. | ||
```js | ||
// Default output is Uint8Array. If you need hex string as an output: | ||
console.log(secp.utils.bytesToHex(pubKey)); | ||
``` | ||
```typescript | ||
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(uint8a: Uint8Array): string; | ||
hexToBytes(hex: string): Uint8Array; | ||
concatBytes(...arrays: Uint8Array[]): Uint8Array; | ||
// Modular division over curve prime | ||
mod: (number: number | bigint, modulo = CURVE.P): bigint; | ||
// Modular inversion | ||
invert(number: bigint, modulo?: bigint): bigint; | ||
sha256: (message: Uint8Array) => Promise<Uint8Array>; | ||
hmacSha256: (key: Uint8Array, ...messages: Uint8Array[]) => Promise<Uint8Array>; | ||
// You can set up your synchronous methods for `signSync`/`signSchnorrSync` to work. | ||
// The argument order is identical to async methods from above | ||
sha256Sync: undefined; | ||
hmacSha256Sync: undefined; | ||
// BIP0340-style tagged hashes | ||
taggedHash: (tag: string, ...messages: Uint8Array[]) => Promise<Uint8Array>; | ||
taggedHashSync: (tag: string, ...messages: Uint8Array[]) => Uint8Array; | ||
// 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; | ||
type Bytes = Uint8Array; | ||
export declare const etc: { | ||
hexToBytes: (hex: string) => Bytes; | ||
bytesToHex: (b: Bytes) => string; | ||
concatBytes: (...arrs: Bytes[]) => Uint8Array; | ||
bytesToNumberBE: (b: Bytes) => bigint; | ||
numberToBytesBE: (num: bigint) => Bytes; | ||
mod: (a: bigint, b?: bigint) => bigint; | ||
invert: (num: bigint, md?: bigint) => bigint; | ||
hmacSha256Async: (key: Bytes, ...msgs: Bytes[]) => Promise<Bytes>; | ||
hmacSha256Sync: HmacFnSync; | ||
hashToPrivateKey: (hash: Hex) => Bytes; | ||
randomBytes: (len: number) => Bytes; | ||
}; | ||
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; | ||
export declare const utils: { | ||
normPrivateKeyToScalar: (p: PrivKey) => bigint; | ||
randomPrivateKey: () => Bytes; | ||
isValidPrivateKey: (key: Hex) => boolean; | ||
precompute(p: Point, windowSize?: number): Point; | ||
}; | ||
class ProjectivePoint { | ||
readonly px: bigint; | ||
readonly py: bigint; | ||
readonly pz: bigint; | ||
constructor(px: bigint, py: bigint, pz: bigint); | ||
static readonly BASE: Point; | ||
static readonly ZERO: Point; | ||
static fromHex(hex: Hex): Point; | ||
static fromPrivateKey(n: PrivKey): Point; | ||
get x(): bigint; | ||
get y(): bigint; | ||
equals(other: Point): boolean; | ||
add(other: Point): Point; | ||
multiply(n: bigint): Point; | ||
negate(): Point; | ||
add(other: Point): Point; | ||
subtract(other: Point): Point; | ||
// Constant-time scalar multiplication. | ||
multiply(scalar: bigint | Uint8Array): Point; | ||
toAffine(): AffinePoint; | ||
assertValidity(): Point; | ||
toHex(isCompressed?: boolean): string; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
} | ||
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; | ||
class Signature { | ||
readonly r: bigint; | ||
readonly s: bigint; | ||
readonly recovery?: number | undefined; | ||
constructor(r: bigint, s: bigint, recovery?: number | undefined); | ||
ok(): Signature; | ||
static fromCompact(hex: Hex): Signature; | ||
hasHighS(): boolean; | ||
recoverPublicKey(msgh: Hex): Point; | ||
toCompactRawBytes(): Uint8Array; | ||
toCompactHex(): string; | ||
} | ||
CURVE // curve prime; order; equation params, generator coordinates | ||
``` | ||
@@ -351,27 +210,41 @@ | ||
Noble is production-ready. | ||
The module is production-ready. | ||
It is cross-tested against [noble-curves](https://github.com/paulmillr/noble-curves), | ||
and has similar security. | ||
1. The library has been audited by an independent security firm cure53: [PDF](https://cure53.de/pentest-report_noble-lib.pdf). See [changes since audit](https://github.com/paulmillr/noble-secp256k1/compare/1.2.0..main). | ||
- The audit has been [crowdfunded](https://gitcoin.co/grants/2451/audit-of-noble-secp256k1-cryptographic-library) by community with help of [Umbra.cash](https://umbra.cash). | ||
2. The library has also been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). You can run the fuzzer by yourself to check it. | ||
1. The current version is rewrite of v1, which has been audited by cure53: | ||
[PDF](https://cure53.de/pentest-report_noble-lib.pdf) (funded by [Umbra.cash](https://umbra.cash) & community). | ||
2. It's being fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz): | ||
run the fuzzer by yourself to check. | ||
We're using built-in JS `BigInt`, which is potentially vulnerable to [timing attacks](https://en.wikipedia.org/wiki/Timing_attack) as [per official spec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#cryptography). 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](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. Nonetheless we've hardened implementation of ec curve multiplication to be algorithmically constant time. | ||
Our EC multiplication is hardened to be algorithmically constant time. | ||
We're using built-in JS `BigInt`, which is potentially vulnerable to | ||
[timing attacks](https://en.wikipedia.org/wiki/Timing_attack) as | ||
[per MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#cryptography). | ||
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](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. | ||
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. | ||
We 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. | ||
## Speed | ||
Benchmarks measured with Apple M2 on MacOS 12 with node.js 18.8. | ||
Use [noble-curves](https://github.com/paulmillr/noble-curves) if you need even higher performance. | ||
getPublicKey(utils.randomPrivateKey()) x 7,093 ops/sec @ 140μs/op | ||
sign x 5,615 ops/sec @ 178μs/op | ||
signSync (@noble/hashes) x 5,209 ops/sec @ 191μs/op | ||
verify x 1,114 ops/sec @ 896μs/op | ||
recoverPublicKey x 1,018 ops/sec @ 982μs/op | ||
getSharedSecret aka ecdh x 665 ops/sec @ 1ms/op | ||
getSharedSecret (precomputed) x 7,426 ops/sec @ 134μs/op | ||
Point.fromHex (decompression) x 14,582 ops/sec @ 68μs/op | ||
schnorr.sign x 805 ops/sec @ 1ms/op | ||
schnorr.verify x 1,129 ops/sec @ 885μs/op | ||
Benchmarks measured with Apple M2 on MacOS 13 with node.js 19. | ||
getPublicKey(utils.randomPrivateKey()) x 5,540 ops/sec @ 180μs/op | ||
sign x 3,301 ops/sec @ 302μs/op | ||
verify x 517 ops/sec @ 1ms/op | ||
getSharedSecret x 433 ops/sec @ 2ms/op | ||
recoverPublicKey x 526 ops/sec @ 1ms/op | ||
Point.fromHex (decompression) x 8,415 ops/sec @ 118μs/op | ||
Compare to other libraries on M1 (`openssl` uses native bindings, not JS): | ||
@@ -398,4 +271,2 @@ | ||
Check out a blog post about this library: [Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). | ||
1. Clone the repository. | ||
@@ -406,6 +277,52 @@ 2. `npm install` to install build dependencies like TypeScript | ||
Special thanks to [Roman Koblov](https://github.com/romankoblov), who have helped to improve scalar multiplication speed. | ||
Special thanks to [Roman Koblov](https://github.com/romankoblov), who have | ||
helped to improve scalar multiplication speed. | ||
## Upgrading | ||
noble-secp256k1 v2 features improved security and smaller attack surface. | ||
The goal of v2 is to provide minimum possible JS library which is safe and fast. | ||
That means the library was reduced 4x, to just over 400 lines. In order to | ||
achieve the goal, **some features were moved** to | ||
[noble-curves](https://github.com/paulmillr/noble-curves), which is | ||
even safer and faster drop-in replacement library with same API. | ||
Switch to curves if you intend to keep using these features: | ||
- DER encoding: toDERHex, toDERRawBytes, signing / verification of DER sigs | ||
- Schnorr signatures | ||
- Using `utils.precompute()` for non-base point | ||
- Support for environments which don't support bigint literals | ||
- Common.js support | ||
- Support for node.js 18 and older without [shim](#usage) | ||
Other changes for upgrading from @noble/secp256k1 1.7 to 2.0: | ||
- `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`, `toCompactRawBytes`, `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) | ||
## License | ||
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
5
Yes
73099
6
1035
323
1