# noble-secp256k1 Fastest 4KB 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) - 🤝 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 - 🪶 3.98KB gzipped (elliptic.js is 12x larger, tiny-secp256k1 is 20-40x larger) The module is a sister project of [noble-curves](https://github.com/paulmillr/noble-curves), focusing on smaller attack surface & better auditability. Curves are drop-in replacement and have more features: MSM, DER encoding, endomorphism, prehashing, custom point precomputes. To upgrade from earlier version, see [Upgrading](#upgrading). 898-byte version of the library is available for learning purposes in `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 with provenance - Check out [homepage](https://paulmillr.com/noble/) & 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), 4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) / [ed25519](https://github.com/paulmillr/noble-ed25519) ## Usage > `npm install @noble/secp256k1` > `deno add jsr:@noble/secp256k1` > `deno doc jsr:@noble/secp256k1` # command-line documentation We support all major platforms and runtimes. For React Native, additional polyfills are needed: see below. ```js import * as secp from '@noble/secp256k1'; (async () => { // Uint8Arrays or hex strings are accepted: // Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) is equal to 'deadbeef' const privKey = secp.utils.randomPrivateKey(); // Secure random private key // sha256 of 'hello world' const msgHash = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; const pubKey = secp.getPublicKey(privKey); const signature = await secp.signAsync(msgHash, privKey); // Sync methods below const isValid = secp.verify(signature, msgHash, pubKey); const alicesPub = secp.getPublicKey(secp.utils.randomPrivateKey()); const shared = secp.getSharedSecret(privKey, alicesPub); // Diffie-Hellman const pub2 = signature.recoverPublicKey(msgHash); // Public key recovery })(); ``` ### Enabling synchronous methods Only async methods are available by default, to keep the library dependency-free. To enable sync methods: ```ts import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)); ``` ### React Native: polyfill getRandomValues and sha512 ```ts import 'react-native-get-random-values'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)); secp.etc.hmacSha256Async = (k, ...m) => Promise.resolve(secp.etc.hmacSha256Sync(k, ...m)); ``` ## API There are 3 main methods: * `getPublicKey(privateKey)` * `sign(messageHash, privateKey)` and `signAsync(messageHash, privateKey)` * `verify(signature, messageHash, publicKey)` Functions generally accept Uint8Array. There are optional utilities which convert hex strings, utf8 strings or bigints to u8a. ### getPublicKey ```ts import { getPublicKey, utils, ProjectivePoint } from '@noble/secp256k1'; const privKey = utils.randomPrivateKey(); const pubKey33b = getPublicKey(privKey); // Variants const pubKey65b = getPublicKey(privKey, false); const pubKeyPoint = ProjectivePoint.fromPrivateKey(privKey); const samePoint = ProjectivePoint.fromHex(pubKeyPoint.toHex()); ``` Generates 33-byte compressed (default) or 65-byte public key from 32-byte private key. ### sign ```ts import * as secp from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import { utf8ToBytes } from '@noble/hashes/utils'; const msg = 'noble cryptography'; const msgHash = sha256(utf8ToBytes(msg)); const priv = secp.utils.randomPrivateKey(); const sigA = secp.sign(msgHash, priv); // Variants const sigB = await secp.signAsync(msgHash, priv); const sigC = secp.sign(msgHash, priv, { extraEntropy: true }); // hedged sig const sigC2 = secp.sign(msgHash, priv, { extraEntropy: Uint8Array.from([0xca, 0xfe]) }); const sigD = secp.sign(msgHash, priv, { lowS: false }); // malleable sig ``` Generates low-s deterministic-k RFC6979 ECDSA signature. Requries hash of message, which means you'll need to do something like `sha256(message)` before signing. `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 '@noble/secp256k1'; const hex = secp.etc.hexToBytes; const sig = hex( 'ddc633c5b48a1a6725c31201892715dda3058350f7b444e89d32c33c90d9c9e218d7eaf02c2254e88c3b33d755394b08bcc7efd13df02338510b750b64572983' ); const msgHash = hex('736403f76264eccc1b77ba58dc8fc690e76b2b1532ba82c736a60f3862082db3'); // const priv = 'd60937c2a1ece169888d4c48717dfcc0e1a7af915505823148cca11859210e9c'; const pubKey = hex('020b6d70b68873ff8fd729adf5cf4bf45021b34236f991768249cba06b11136ec6'); // verify const isValid = secp.verify(sig, msgHash, pubKey); const isValidLoose = secp.verify(sig, msgHash, pubKey, { lowS: false }); ``` Verifies ECDSA signature. 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 '@noble/secp256k1'; const bobsPriv = secp.utils.randomPrivateKey(); const alicesPub = secp.getPublicKey(secp.utils.randomPrivateKey()); // ECDH between Alice and Bob const shared33b = secp.getSharedSecret(bobsPriv, alicesPub); const shared65b = secp.getSharedSecret(bobsPriv, alicesPub, false); const sharedPoint = secp.ProjectivePoint.fromHex(alicesPub).multiply(bobsPriv); ``` Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between key A and different key B. ### recoverPublicKey ```ts import * as secp from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import { utf8ToBytes } from '@noble/hashes/utils'; const msg = 'noble cryptography'; const msgHash = sha256(utf8ToBytes(msg)); const priv = secp.utils.randomPrivateKey(); const pub1 = secp.getPubkicKey(priv); const sig = secp.sign(msgHash, priv); const pub2 = sig.recoverPublicKey(msgHash); ``` Recover public key from Signature instance with `recovery` bit set. ### utils A bunch of useful **utilities** are also exposed: ```typescript type Bytes = Uint8Array; const etc: { hexToBytes: (hex: string) => Bytes; bytesToHex: (b: Bytes) => string; concatBytes: (...arrs: Bytes[]) => Bytes; 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; hmacSha256Sync: HmacFnSync; hashToPrivateKey: (hash: Hex) => Bytes; randomBytes: (len: number) => Bytes; }; const utils: { normPrivateKeyToScalar: (p: PrivKey) => bigint; randomPrivateKey: () => Bytes; // Uses CSPRNG https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues isValidPrivateKey: (key: Hex) => boolean; precompute(p: ProjectivePoint, windowSize?: number): ProjectivePoint; }; class ProjectivePoint { constructor(px: bigint, py: bigint, pz: bigint); static readonly BASE: ProjectivePoint; static readonly ZERO: ProjectivePoint; static fromAffine(point: AffinePoint): ProjectivePoint; static fromHex(hex: Hex): ProjectivePoint; static fromPrivateKey(n: PrivKey): ProjectivePoint; get x(): bigint; get y(): bigint; add(other: ProjectivePoint): ProjectivePoint; assertValidity(): void; equals(other: ProjectivePoint): boolean; multiply(n: bigint): ProjectivePoint; negate(): ProjectivePoint; subtract(other: ProjectivePoint): ProjectivePoint; toAffine(): AffinePoint; toHex(isCompressed?: boolean): string; toRawBytes(isCompressed?: boolean): Bytes; } class Signature { constructor(r: bigint, s: bigint, recovery?: number | undefined); static fromCompact(hex: Hex): Signature; readonly r: bigint; readonly s: bigint; readonly recovery?: number | undefined; ok(): Signature; hasHighS(): boolean; normalizeS(): Signature; recoverPublicKey(msgh: Hex): Point; toCompactRawBytes(): Bytes; toCompactHex(): string; } CURVE; // curve prime; order; equation params, generator coordinates ``` ## 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. Make sure to verify commit signatures - **Releases** are transparent and built on GitHub CI. Check out [attested checksums of single-file builds](https://github.com/paulmillr/noble-secp256k1/attestations) and [provenance logs](https://github.com/paulmillr/noble-secp256k1/actions/workflows/release.yml) - **Rare releasing** is followed to ensure less re-audit need for end-users - **Dependencies** are minimized and locked-down: any dependency could get hacked and users will be downloading malware with every install. - We make sure to use as few dependencies as possible - Automatic dep updates are prevented by locking-down version ranges; diffs are checked with `npm-diff` - **Dev Dependencies** are disabled for end-users; they are only used to develop / build the source code For this package, there are 0 dependencies; and a few dev dependencies: - [noble-hashes](https://github.com/paulmillr/noble-hashes) provides cryptographic hashing functionality - micro-bmark, micro-should and jsbt are 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. It's hard to audit their source code thoroughly and fully because of their size ### Randomness We're deferring to built-in [crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) which is considered cryptographically secure (CSPRNG). In the past, browsers had bugs that made it weak: it may happen again. Implementing a userspace CSPRNG to get resilient to the weakness is even worse: there is no reliable userspace source of 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). ## Speed npm run bench Benchmarks measured with Apple M4. [noble-curves](https://github.com/paulmillr/noble-curves) enable faster performance. ``` getPublicKey(utils.randomPrivateKey()) x 8,770 ops/sec @ 114μs/op signAsync x 4,848 ops/sec @ 206μs/op sign x 7,261 ops/sec @ 137μs/op verify x 817 ops/sec @ 1ms/op getSharedSecret x 688 ops/sec @ 1ms/op recoverPublicKey x 839 ops/sec @ 1ms/op Point.fromHex (decompression) x 12,937 ops/sec @ 77μs/op ``` ## Upgrading ### v1 to v2 noble-secp256k1 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, 4KB 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`, `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) ## Contributing & testing - `npm install && npm run build && npm test` will build the code and run tests. - `npm run bench` will run benchmarks, which may need their deps first (`npm run bench:install`) - `npm run loc` will count total output size, important to be less than 4KB Check out [github.com/paulmillr/guidelines](https://github.com/paulmillr/guidelines) for general coding practices and rules. See [paulmillr.com/noble](https://paulmillr.com/noble/) for useful resources, articles, documentation and demos related to the library. ## License MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.