1
1
# noble-secp256k1
2
2
3
- Fastest 4KB JS implementation of secp256k1 signatures & ECDH.
3
+ Fastest 5KB JS implementation of secp256k1 signatures & ECDH.
4
4
5
5
- ✍️ [ ECDSA] ( https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm )
6
6
signatures compliant with [ RFC6979] ( https://www.rfc-editor.org/rfc/rfc6979 )
7
+ - ➰ Schnorr
8
+ signatures compliant with [ BIP340] ( https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki )
7
9
- 🤝 Elliptic Curve Diffie-Hellman [ ECDH] ( https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman )
8
10
- 🔒 Supports [ hedged signatures] ( https://paulmillr.com/posts/deterministic-signatures/ ) guarding against fault attacks
9
- - 🪶 3.98KB gzipped ( elliptic.js is 12x larger, tiny-secp256k1 is 20-40x larger)
11
+ - 🪶 4.86KB ( gzipped, elliptic.js is 10x larger, tiny-secp256k1 is 25x larger)
10
12
11
13
The module is a sister project of [ noble-curves] ( https://github.com/paulmillr/noble-curves ) ,
12
14
focusing on smaller attack surface & better auditability.
13
15
Curves are drop-in replacement and have more features:
14
- MSM, DER encoding, endomorphism, prehashing, custom point precomputes.
16
+ MSM, DER encoding, endomorphism, prehashing, custom point precomputes, hash-to-curve, oprf .
15
17
To upgrade from earlier version, see [ Upgrading] ( #upgrading ) .
16
18
17
19
898-byte version of the library is available for learning purposes in ` test/misc/1kb.min.js ` ,
@@ -45,18 +47,16 @@ We support all major platforms and runtimes. For React Native, additional polyfi
45
47
``` js
46
48
import * as secp from ' @noble/secp256k1' ;
47
49
(async () => {
48
- // Uint8Arrays or hex strings are accepted:
49
- // Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) is equal to 'deadbeef'
50
- const privKey = secp .utils .randomSecretKey (); // Secure random private key
51
- // sha256 of 'hello world'
52
- const msgHash = ' b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9' ;
53
- const pubKey = secp .getPublicKey (privKey);
54
- const signature = await secp .signAsync (msgHash, privKey); // Sync methods below
55
- const isValid = secp .verify (signature, msgHash, pubKey);
56
-
57
- const alicesPub = secp .getPublicKey (secp .utils .randomSecretKey ());
58
- const shared = secp .getSharedSecret (privKey, alicesPub); // Diffie-Hellman
59
- const pub2 = signature .recoverPublicKey (msgHash); // Public key recovery
50
+ const { secretKey , publicKey } = secp .keygen ();
51
+ // const publicKey = secp.getPublicKey(secretKey);
52
+ const msg = new TextEncoder ().encode (' hello noble' );
53
+ const sig = await secp .signAsync (msg, secretKey);
54
+ const isValid = await secp .verifyAsync (sig, msg, publicKey);
55
+
56
+ const bobsKeys = secp .keygen ();
57
+ const shared = secp .getSharedSecret (secretKey, bobsKeys .publicKey ); // Diffie-Hellman
58
+ const sigr = await secp .signAsync (msg, secretKey, { format: ' recovered' });
59
+ const publicKey2 = secp .recoverPublicKey (sigr, msg);
60
60
})();
61
61
```
62
62
@@ -68,41 +68,52 @@ To enable sync methods:
68
68
``` ts
69
69
import { hmac } from ' @noble/hashes/hmac.js' ;
70
70
import { sha256 } from ' @noble/hashes/sha2.js' ;
71
- secp .etc .hmacSha256Sync = (k , ... m ) => hmac (sha256 , k , secp .etc .concatBytes (... m ));
71
+ secp .hashes .hmacSha256 = (key , msg ) => hmac (sha256 , key , msg );
72
+ secp .hashes .sha256 = sha256 ;
72
73
```
73
74
74
- ### React Native: polyfill getRandomValues and sha512
75
+ ### React Native: polyfill getRandomValues and sha256
75
76
76
77
``` ts
77
78
import ' react-native-get-random-values' ;
78
79
import { hmac } from ' @noble/hashes/hmac.js' ;
79
80
import { sha256 } from ' @noble/hashes/sha2.js' ;
80
- secp .etc .hmacSha256Sync = (k , ... m ) => hmac (sha256 , k , secp .etc .concatBytes (... m ));
81
- secp .etc .hmacSha256Async = (k , ... m ) => Promise .resolve (secp .etc .hmacSha256Sync (k , ... m ));
81
+ secp .hashes .hmacSha256 = (key , msg ) => hmac (sha256 , key , msg );
82
+ secp .hashes .sha256 = sha256 ;
83
+ secp .hashes .hmacSha256Async = async (key , msg ) => hmac (sha256 , key , msg );
84
+ secp .hashes .sha256Async = async (msg ) => sha256 (msg );
82
85
```
83
86
84
87
## API
85
88
86
- There are 3 main methods:
89
+ There are 4 main methods, which accept Uint8Array-s :
87
90
88
- * ` getPublicKey(privateKey) `
89
- * ` sign(messageHash, privateKey) ` and ` signAsync(messageHash, privateKey) `
90
- * ` verify(signature, messageHash, publicKey) `
91
+ * ` keygen() `
92
+ * ` getPublicKey(secretKey) `
93
+ * ` sign(messageHash, secretKey) ` and ` signAsync(messageHash, secretKey) `
94
+ * ` verify(signature, messageHash, publicKey) ` and ` verifyAsync(signature, messageHash, publicKey) `
91
95
92
- Functions generally accept Uint8Array.
93
- There are optional utilities which convert hex strings, utf8 strings or bigints to u8a.
96
+ ### keygen
97
+
98
+ ``` ts
99
+ import * as secp from ' @noble/secp256k1' ;
100
+ (async () => {
101
+ const keys = secp .keygen ();
102
+ const { secretKey, publicKey } = keys ;
103
+ })();
104
+ ```
94
105
95
106
### getPublicKey
96
107
97
108
``` ts
98
- import { getPublicKey , utils , ProjectivePoint } from ' @noble/secp256k1' ;
99
- const privKey = utils .randomSecretKey ();
100
- const pubKey33b = getPublicKey (privKey );
109
+ import * as secp from ' @noble/secp256k1' ;
110
+ const secretKey = secp . utils .randomSecretKey ();
111
+ const pubKey33b = secp . getPublicKey (secretKey );
101
112
102
113
// Variants
103
- const pubKey65b = getPublicKey (privKey , false );
104
- const pubKeyPoint = ProjectivePoint . fromPrivateKey ( privKey );
105
- const samePoint = ProjectivePoint . fromHex ( pubKeyPoint .toHex () );
114
+ const pubKey65b = secp . getPublicKey (secretKey , false );
115
+ const pubKeyPoint = secp . Point . fromBytes ( pubKey65b );
116
+ const samePoint = pubKeyPoint .toBytes ( );
106
117
```
107
118
108
119
Generates 33-byte compressed (default) or 65-byte public key from 32-byte private key.
@@ -111,67 +122,69 @@ Generates 33-byte compressed (default) or 65-byte public key from 32-byte privat
111
122
112
123
``` ts
113
124
import * as secp from ' @noble/secp256k1' ;
114
- import { sha256 } from ' @noble/hashes/sha256' ;
115
- import { utf8ToBytes } from ' @noble/hashes/utils' ;
116
- const msg = ' noble cryptography' ;
117
- const msgHash = sha256 (utf8ToBytes (msg ));
118
- const priv = secp .utils .randomSecretKey ();
119
-
120
- const sigA = secp .sign (msgHash , priv );
121
-
122
- // Variants
123
- const sigB = await secp .signAsync (msgHash , priv );
124
- const sigC = secp .sign (msgHash , priv , { extraEntropy: true }); // hedged sig
125
- const sigC2 = secp .sign (msgHash , priv , { extraEntropy: Uint8Array .from ([0xca , 0xfe ]) });
126
- const sigD = secp .sign (msgHash , priv , { lowS: false }); // malleable sig
125
+ const { secretKey } = secp .keygen ();
126
+ const msg = ' hello noble' ;
127
+ const sig = secp .sign (msg , secretKey );
128
+
129
+ // async
130
+ const sigB = await secp .signAsync (msg , secretKey );
131
+
132
+ // recovered, allows `recoverPublicKey(sigR, msg)`
133
+ const sigR = secp .sign (msg , secretKey , { format: ' recovered' });
134
+ // custom hash
135
+ import { keccak256 } from ' @noble/hashes/sha3.js' ;
136
+ const sigH = secp .sign (keccak256 (msg ), secretKey , { prehash: false });
137
+ // hedged sig
138
+ const sigC = secp .sign (msg , secretKey , { extraEntropy: true });
139
+ const sigC2 = secp .sign (msg , secretKey , { extraEntropy: Uint8Array .from ([0xca , 0xfe ]) });
140
+ // malleable sig
141
+ const sigD = secp .sign (msg , secretKey , { lowS: false });
127
142
```
128
143
129
- Generates low-s deterministic-k RFC6979 ECDSA signature. Requries hash of message,
130
- which means you'll need to do something like ` sha256(message) ` before signing.
144
+ Generates low-s deterministic-k RFC6979 ECDSA signature.
131
145
132
- ` extraEntropy: true ` enables hedged signatures. They incorporate
146
+ - Message will be hashed with sha256. If you want to use a different hash function,
147
+ make sure to use ` { prehash: false } ` .
148
+ - ` extraEntropy: true ` enables hedged signatures. They incorporate
133
149
extra randomness into RFC6979 (described in section 3.6),
134
150
to provide additional protection against fault attacks.
135
151
Check out blog post [ Deterministic signatures are not your friends] ( https://paulmillr.com/posts/deterministic-signatures/ ) .
136
152
Even if their RNG is broken, they will fall back to determinism.
137
-
138
- Default behavior ` lowS: true ` prohibits signatures which have (sig.s >= CURVE.n/2n) and is compatible with BTC/ETH.
139
- Setting ` lowS: false ` allows to create malleable signatures, which is default openssl behavior.
140
- Non-malleable signatures can still be successfully verified in openssl.
153
+ - 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.
141
154
142
155
### verify
143
156
144
157
``` ts
145
158
import * as secp from ' @noble/secp256k1' ;
146
- const hex = secp .etc .hexToBytes ;
147
- const sig = hex (
148
- ' ddc633c5b48a1a6725c31201892715dda3058350f7b444e89d32c33c90d9c9e218d7eaf02c2254e88c3b33d755394b08bcc7efd13df02338510b750b64572983'
149
- );
150
- const msgHash = hex (' 736403f76264eccc1b77ba58dc8fc690e76b2b1532ba82c736a60f3862082db3' );
151
- // const priv = 'd60937c2a1ece169888d4c48717dfcc0e1a7af915505823148cca11859210e9c';
152
- const pubKey = hex (' 020b6d70b68873ff8fd729adf5cf4bf45021b34236f991768249cba06b11136ec6' );
153
-
154
- // verify
155
- const isValid = secp .verify (sig , msgHash , pubKey );
156
- const isValidLoose = secp .verify (sig , msgHash , pubKey , { lowS: false });
159
+ const { secretKey, publicKey } = secp .keygen ();
160
+ const msg = ' hello noble' ;
161
+ const sig = secp .sign (msg , secretKey );
162
+ const isValid = secp .verify (sig , msg , publicKey );
163
+
164
+ // custom hash
165
+ import { keccak256 } from ' @noble/hashes/sha3.js' ;
166
+ const sigH = secp .sign (keccak256 (msg ), secretKey , { prehash: false });
157
167
```
158
168
159
169
Verifies ECDSA signature.
160
- Default behavior ` lowS: true ` prohibits malleable signatures which have (` sig.s >= CURVE.n/2n ` ) and
161
- is compatible with BTC / ETH.
162
- Setting ` lowS: false ` allows to create signatures, which is default openssl behavior.
170
+
171
+ - Message will be hashed with sha256. If you want to use a different hash function,
172
+ make sure to use ` { prehash: false } ` .
173
+ - Default behavior ` lowS: true ` prohibits malleable signatures which have (` sig.s >= CURVE.n/2n ` ) and
174
+ is compatible with BTC / ETH.
175
+ Setting ` lowS: false ` allows to create signatures, which is default openssl behavior.
163
176
164
177
### getSharedSecret
165
178
166
179
``` ts
167
180
import * as secp from ' @noble/secp256k1' ;
168
- const bobsPriv = secp .utils . randomSecretKey ();
169
- const alicesPub = secp .getPublicKey ( secp . utils . randomSecretKey () );
170
-
171
- // ECDH between Alice and Bob
172
- const shared33b = secp .getSharedSecret ( bobsPriv , alicesPub );
173
- const shared65b = secp .getSharedSecret ( bobsPriv , alicesPub , false );
174
- const sharedPoint = secp . ProjectivePoint . fromHex ( alicesPub ). multiply ( bobsPriv );
181
+ const alice = secp .keygen ();
182
+ const bob = secp .keygen ( );
183
+ const shared33b = secp . getSharedSecret ( alice . secretKey , bob . publicKey );
184
+ const shared65b = secp . getSharedSecret ( bob . secretKey , alice . publicKey , false );
185
+ const sharedPoint = secp .Point . fromBytes ( bob . publicKey ). multiply (
186
+ secp .etc . secretKeyToScalar ( alice . secretKey )
187
+ );
175
188
```
176
189
177
190
Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between
@@ -182,15 +195,15 @@ key A and different key B.
182
195
``` ts
183
196
import * as secp from ' @noble/secp256k1' ;
184
197
185
- import { sha256 } from ' @noble/hashes/sha256' ;
186
- import { utf8ToBytes } from ' @noble/hashes/utils' ;
187
- const msg = ' noble cryptography' ;
188
- const msgHash = sha256 (utf8ToBytes (msg ));
189
- const priv = secp .utils .randomSecretKey ();
190
- const pub1 = secp .getPubkicKey (priv );
191
- const sig = secp .sign (msgHash , priv );
198
+ const { secretKey, publicKey } = secp .keygen ();
199
+ const msg = ' hello noble' ;
200
+ const sigR = secp .sign (msg , secretKey , { format: ' recovered' });
201
+ const publicKey2 = secp .recoverPublicKey (sigR , msg );
192
202
193
- const pub2 = sig .recoverPublicKey (msgHash );
203
+ // custom hash
204
+ import { keccak256 } from ' @noble/hashes/sha3.js' ;
205
+ const sigR = secp .sign (keccak256 (msg ), secretKey , { format: ' recovered' , prehash: false });
206
+ const publicKey2 = secp .recoverPublicKey (sigR , keccak256 (msg ), { prehash: false });
194
207
```
195
208
196
209
Recover public key from Signature instance with ` recovery ` bit set.
@@ -199,60 +212,41 @@ Recover public key from Signature instance with `recovery` bit set.
199
212
200
213
A bunch of useful ** utilities** are also exposed:
201
214
202
- ``` typescript
203
- type Bytes = Uint8Array ;
204
- const etc: {
205
- hexToBytes: (hex : string ) => Bytes ;
206
- bytesToHex: (b : Bytes ) => string ;
207
- concatBytes: (... arrs : Bytes []) => Bytes ;
208
- bytesToNumberBE: (b : Bytes ) => bigint ;
209
- numberToBytesBE: (num : bigint ) => Bytes ;
210
- mod: (a : bigint , b ? : bigint ) => bigint ;
211
- invert: (num : bigint , md ? : bigint ) => bigint ;
212
- hmacSha256Async: (key : Bytes , ... msgs : Bytes []) => Promise <Bytes >;
213
- hmacSha256Sync: HmacFnSync ;
214
- hashToPrivateKey: (hash : Hex ) => Bytes ;
215
- randomBytes: (len : number ) => Bytes ;
216
- };
217
- const utils: {
218
- normPrivateKeyToScalar: (p : PrivKey ) => bigint ;
219
- randomSecretKey: () => Bytes ; // Uses CSPRNG https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
220
- isValidPrivateKey: (key : Hex ) => boolean ;
221
- precompute(p : ProjectivePoint , windowSize ? : number ): ProjectivePoint ;
222
- };
223
- class ProjectivePoint {
224
- constructor (px : bigint , py : bigint , pz : bigint );
225
- static readonly BASE: ProjectivePoint ;
226
- static readonly ZERO: ProjectivePoint ;
227
- static fromAffine(point : AffinePoint ): ProjectivePoint ;
228
- static fromHex(hex : Hex ): ProjectivePoint ;
229
- static fromPrivateKey(n : PrivKey ): ProjectivePoint ;
215
+ ``` ts
216
+ import * as secp from ' @noble/secp256k1' ;
217
+
218
+ const { bytesToHex, hexToBytes, concatBytes, mod, invert, randomBytes } = secp .etc ;
219
+ const { isValidSecretKey, isValidPublicKey, randomSecretKey } = secp .utils ;
220
+ const { Point } = secp ;
221
+ console .log (Point .CURVE (), Point .BASE );
222
+ /*
223
+ class Point {
224
+ static BASE: Point;
225
+ static ZERO: Point;
226
+ readonly X: bigint;
227
+ readonly Y: bigint;
228
+ readonly Z: bigint;
229
+ constructor(X: bigint, Y: bigint, Z: bigint);
230
+ static CURVE(): WeierstrassOpts<bigint>;
231
+ static fromAffine(ap: AffinePoint): Point;
232
+ static fromBytes(bytes: Bytes): Point;
233
+ static fromHex(hex: string): Point;
230
234
get x(): bigint;
231
235
get y(): bigint;
232
- add(other : ProjectivePoint ): ProjectivePoint ;
233
- assertValidity(): void ;
234
- equals(other : ProjectivePoint ): boolean ;
235
- multiply(n : bigint ): ProjectivePoint ;
236
- negate(): ProjectivePoint ;
237
- subtract(other : ProjectivePoint ): ProjectivePoint ;
236
+ equals(other: Point): boolean;
237
+ is0(): boolean;
238
+ negate(): Point;
239
+ double(): Point;
240
+ add(other: Point): Point;
241
+ subtract(other: Point): Point;
242
+ multiply(n: bigint): Point;
243
+ multiplyUnsafe(scalar: bigint): Point;
238
244
toAffine(): AffinePoint;
245
+ assertValidity(): Point;
246
+ toBytes(isCompressed?: boolean): Bytes;
239
247
toHex(isCompressed?: boolean): string;
240
- toRawBytes(isCompressed ? : boolean ): Bytes ;
241
- }
242
- class Signature {
243
- constructor (r : bigint , s : bigint , recovery ? : number | undefined );
244
- static fromCompact(hex : Hex ): Signature ;
245
- readonly r: bigint ;
246
- readonly s: bigint ;
247
- readonly recovery? : number | undefined ;
248
- ok(): Signature ;
249
- hasHighS(): boolean ;
250
- normalizeS(): Signature ;
251
- recoverPublicKey(msgh : Hex ): Point ;
252
- toBytes(): Bytes ;
253
- toCompactHex(): string ;
254
248
}
255
- CURVE ; // curve prime; order; equation params, generator coordinates
249
+ */
256
250
```
257
251
258
252
## Security
@@ -320,13 +314,16 @@ NIST prohibits classical cryptography (RSA, DSA, ECDSA, ECDH) [after 2035](https
320
314
Benchmarks measured with Apple M4. [ noble-curves] ( https://github.com/paulmillr/noble-curves ) enable faster performance.
321
315
322
316
```
323
- getPublicKey(utils.randomSecretKey()) x 8,770 ops/sec @ 114μs/op
324
- signAsync x 4,848 ops/sec @ 206μs/op
325
- sign x 7,261 ops/sec @ 137μs/op
326
- verify x 817 ops/sec @ 1ms/op
327
- getSharedSecret x 688 ops/sec @ 1ms/op
328
- recoverPublicKey x 839 ops/sec @ 1ms/op
329
- Point.fromHex (decompression) x 12,937 ops/sec @ 77μs/op
317
+ keygen x 7,643 ops/sec @ 130μs/op
318
+ sign x 7,620 ops/sec @ 131μs/op
319
+ verify x 823 ops/sec @ 1ms/op
320
+ getSharedSecret x 707 ops/sec @ 1ms/op
321
+ recoverPublicKey x 790 ops/sec @ 1ms/op
322
+
323
+ signAsync x 4,874 ops/sec @ 205μs/op
324
+ verifyAsync x 811 ops/sec @ 1ms/op
325
+
326
+ Point.fromBytes x 13,656 ops/sec @ 73μs/op
330
327
```
331
328
332
329
## Upgrading
@@ -374,7 +371,7 @@ The goal of v2 is to provide minimum possible JS library which is safe and fast.
374
371
375
372
- ` npm install && npm run build && npm test ` will build the code and run tests.
376
373
- ` npm run bench ` will run benchmarks, which may need their deps first (` npm run bench:install ` )
377
- - ` npm run loc ` will count total output size, important to be less than 4KB
374
+ - ` npm run build:release ` will build single non-module file
378
375
379
376
Check out [ github.com/paulmillr/guidelines] ( https://github.com/paulmillr/guidelines )
380
377
for general coding practices and rules.
0 commit comments