@noble/curves 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -164
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +2 -2
- package/abstract/bls.js.map +1 -1
- package/abstract/hash-to-curve.d.ts +1 -1
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +14 -8
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +51 -11
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +79 -21
- package/abstract/modular.js.map +1 -1
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +39 -41
- package/abstract/poseidon.js.map +1 -1
- package/abstract/utils.d.ts +1 -0
- package/abstract/utils.d.ts.map +1 -1
- package/abstract/utils.js +2 -1
- package/abstract/utils.js.map +1 -1
- package/abstract/weierstrass.d.ts +2 -1
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +13 -11
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +7 -8
- package/bls12-381.js.map +1 -1
- package/ed25519.d.ts +1 -0
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +9 -6
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +51 -2
- package/ed448.d.ts.map +1 -1
- package/ed448.js +206 -28
- package/ed448.js.map +1 -1
- package/esm/abstract/bls.js +3 -3
- package/esm/abstract/bls.js.map +1 -1
- package/esm/abstract/hash-to-curve.js +14 -8
- package/esm/abstract/hash-to-curve.js.map +1 -1
- package/esm/abstract/modular.js +75 -20
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/poseidon.js +39 -41
- package/esm/abstract/poseidon.js.map +1 -1
- package/esm/abstract/utils.js +2 -1
- package/esm/abstract/utils.js.map +1 -1
- package/esm/abstract/weierstrass.js +13 -11
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.js +7 -8
- package/esm/bls12-381.js.map +1 -1
- package/esm/ed25519.js +9 -6
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.js +208 -31
- package/esm/ed448.js.map +1 -1
- package/esm/jubjub.js +1 -1
- package/esm/jubjub.js.map +1 -1
- package/esm/package.json +1 -4
- package/jubjub.js.map +1 -1
- package/package.json +4 -3
- package/src/abstract/bls.ts +3 -3
- package/src/abstract/hash-to-curve.ts +14 -8
- package/src/abstract/modular.ts +81 -22
- package/src/abstract/poseidon.ts +39 -40
- package/src/abstract/utils.ts +4 -1
- package/src/abstract/weierstrass.ts +13 -11
- package/src/bls12-381.ts +7 -8
- package/src/ed25519.ts +9 -6
- package/src/ed448.ts +251 -33
- package/src/jubjub.ts +1 -1
package/README.md
CHANGED
|
@@ -9,26 +9,23 @@ Audited & minimal JS implementation of elliptic curve cryptography.
|
|
|
9
9
|
- ➰ Short Weierstrass, Edwards, Montgomery curves
|
|
10
10
|
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
|
11
11
|
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
|
|
12
|
-
- #️⃣
|
|
13
|
-
for encoding or hashing an arbitrary string to an elliptic curve point
|
|
12
|
+
- #️⃣ hash-to-curve for encoding or hashing an arbitrary string to an elliptic curve point
|
|
14
13
|
- 🧜♂️ Poseidon ZK-friendly hash
|
|
15
14
|
|
|
16
|
-
Check out [Upgrading](#upgrading) if you've previously used single-feature noble
|
|
17
|
-
packages. See [Resources](#resources) for articles and real-world software that uses curves.
|
|
18
|
-
|
|
19
15
|
### This library belongs to _noble_ crypto
|
|
20
16
|
|
|
21
17
|
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
|
22
18
|
|
|
23
19
|
- No dependencies, protection against supply chain attacks
|
|
24
20
|
- Auditable TypeScript / JS code
|
|
25
|
-
- Supported
|
|
26
|
-
-
|
|
21
|
+
- Supported on all major platforms
|
|
22
|
+
- Releases are signed with PGP keys and built transparently with NPM provenance
|
|
27
23
|
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
[
|
|
31
|
-
[
|
|
24
|
+
[ciphers](https://github.com/paulmillr/noble-ciphers),
|
|
25
|
+
[curves](https://github.com/paulmillr/noble-curves),
|
|
26
|
+
[hashes](https://github.com/paulmillr/noble-hashes),
|
|
27
|
+
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
|
|
28
|
+
[ed25519](https://github.com/paulmillr/noble-ed25519)
|
|
32
29
|
|
|
33
30
|
## Usage
|
|
34
31
|
|
|
@@ -39,33 +36,52 @@ For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/m
|
|
|
39
36
|
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
|
40
37
|
If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
- [Implementations](#implementations)
|
|
40
|
+
- [ECDSA signature scheme](#ecdsa-signature-scheme)
|
|
41
|
+
- [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy)
|
|
42
|
+
- [ECDH (Elliptic Curve Diffie-Hellman)](#ecdh-elliptic-curve-diffie-hellman)
|
|
43
|
+
- [Schnorr signatures over secp256k1, BIP340](#schnorr-signatures-over-secp256k1-bip340)
|
|
44
|
+
- [ed25519, X25519, ristretto255](#ed25519-x25519-ristretto255)
|
|
45
|
+
- [ed448, X448, decaf448](#ed448-x448-decaf448)
|
|
46
|
+
- [bls12-381](#bls12-381)
|
|
47
|
+
- [All available imports](#all-available-imports)
|
|
48
|
+
- [Accessing a curve's variables](#accessing-a-curves-variables)
|
|
49
|
+
- [Abstract API](#abstract-api)
|
|
50
|
+
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
|
51
|
+
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
|
52
|
+
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
|
53
|
+
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
|
|
54
|
+
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
|
55
|
+
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
|
56
|
+
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
|
57
|
+
- [Creating private keys from hashes](#creating-private-keys-from-hashes)
|
|
58
|
+
- [abstract/utils: Useful utilities](#abstractutils-useful-utilities)
|
|
59
|
+
- [Security](#security)
|
|
60
|
+
- [Speed](#speed)
|
|
61
|
+
- [Contributing & testing](#contributing--testing)
|
|
62
|
+
- [Upgrading](#upgrading)
|
|
63
|
+
- [Resources](#resources)
|
|
64
|
+
- [Demos](#demos)
|
|
65
|
+
- [Projects using curves](#projects-using-curves)
|
|
66
|
+
- [License](#license)
|
|
45
67
|
|
|
46
|
-
|
|
68
|
+
### Implementations
|
|
47
69
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521
|
|
51
|
-
- SECG curve secp256k1
|
|
52
|
-
- ed25519 / curve25519 / x25519 / ristretto255, edwards448 / curve448 / x448
|
|
53
|
-
- pairing-friendly curves bls12-381, bn254
|
|
54
|
-
- [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves
|
|
55
|
-
2. [Abstract](#abstract-api), zero-dependency elliptic curve algorithms
|
|
70
|
+
Implementations are utilizing [noble-hashes](https://github.com/paulmillr/noble-hashes).
|
|
71
|
+
[Abstract API](#abstract-api) doesn't depend on them: you can use a different hashing library.
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
#### ECDSA signature scheme
|
|
58
74
|
|
|
59
|
-
|
|
75
|
+
Generic example that works for all curves, shown for secp256k1:
|
|
60
76
|
|
|
61
77
|
```ts
|
|
62
|
-
//
|
|
78
|
+
// import * from '@noble/curves'; // Error: use sub-imports, to ensure small app size
|
|
63
79
|
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
|
|
64
80
|
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
|
|
65
81
|
const priv = secp256k1.utils.randomPrivateKey();
|
|
66
82
|
const pub = secp256k1.getPublicKey(priv);
|
|
67
|
-
const msg = new Uint8Array(32).fill(1);
|
|
68
|
-
const sig = secp256k1.sign(msg, priv);
|
|
83
|
+
const msg = new Uint8Array(32).fill(1); // message hash (not message) in ecdsa
|
|
84
|
+
const sig = secp256k1.sign(msg, priv); // `{prehash: true}` option is available
|
|
69
85
|
const isValid = secp256k1.verify(sig, msg, pub) === true;
|
|
70
86
|
|
|
71
87
|
// hex strings are also supported besides Uint8Arrays:
|
|
@@ -73,29 +89,22 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
|
|
|
73
89
|
const pub2 = secp256k1.getPublicKey(privHex);
|
|
74
90
|
```
|
|
75
91
|
|
|
76
|
-
####
|
|
92
|
+
#### ECDSA public key recovery & extra entropy
|
|
77
93
|
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
import { p384 } from '@noble/curves/p384';
|
|
84
|
-
import { p521 } from '@noble/curves/p521';
|
|
85
|
-
import { pallas, vesta } from '@noble/curves/pasta';
|
|
86
|
-
import { bls12_381 } from '@noble/curves/bls12-381';
|
|
87
|
-
import { bn254 } from '@noble/curves/bn254';
|
|
88
|
-
import { jubjub } from '@noble/curves/jubjub';
|
|
94
|
+
```ts
|
|
95
|
+
sig.recoverPublicKey(msg).toRawBytes(); // === pub; // public key recovery
|
|
96
|
+
|
|
97
|
+
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
|
|
98
|
+
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
|
|
89
99
|
```
|
|
90
100
|
|
|
91
|
-
####
|
|
101
|
+
#### ECDH (Elliptic Curve Diffie-Hellman)
|
|
92
102
|
|
|
93
103
|
```ts
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
sig.recoverPublicKey(msg) === pub; // public key recovery
|
|
104
|
+
// 1. The output includes parity byte. Strip it using shared.slice(1)
|
|
105
|
+
// 2. The output is not hashed. More secure way is sha256(shared) or hkdf(shared)
|
|
97
106
|
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
|
98
|
-
const shared = secp256k1.getSharedSecret(priv, someonesPub);
|
|
107
|
+
const shared = secp256k1.getSharedSecret(priv, someonesPub);
|
|
99
108
|
```
|
|
100
109
|
|
|
101
110
|
#### Schnorr signatures over secp256k1 (BIP340)
|
|
@@ -129,7 +138,6 @@ It has SUF-CMA (strong unforgeability under chosen message attacks).
|
|
|
129
138
|
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
|
|
130
139
|
|
|
131
140
|
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
|
|
132
|
-
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
|
133
141
|
|
|
134
142
|
```ts
|
|
135
143
|
// Variants from RFC8032: with context, prehashed
|
|
@@ -147,17 +155,36 @@ x25519.getPublicKey(x25519.utils.randomPrivateKey());
|
|
|
147
155
|
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
|
|
148
156
|
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
|
|
149
157
|
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
|
|
158
|
+
```
|
|
150
159
|
|
|
160
|
+
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
|
161
|
+
|
|
162
|
+
```ts
|
|
151
163
|
// hash-to-curve, ristretto255
|
|
152
|
-
import {
|
|
164
|
+
import { utf8ToBytes } from '@noble/hashes/utils';
|
|
165
|
+
import { sha512 } from '@noble/hashes/sha512';
|
|
166
|
+
import {
|
|
167
|
+
hashToCurve,
|
|
168
|
+
encodeToCurve,
|
|
169
|
+
RistrettoPoint,
|
|
170
|
+
hashToRistretto255,
|
|
171
|
+
} from '@noble/curves/ed25519';
|
|
172
|
+
|
|
173
|
+
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
|
|
174
|
+
hashToCurve(msg);
|
|
175
|
+
|
|
153
176
|
const rp = RistrettoPoint.fromHex(
|
|
154
177
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
|
|
155
178
|
);
|
|
156
|
-
RistrettoPoint.
|
|
157
|
-
|
|
179
|
+
RistrettoPoint.BASE.multiply(2n).add(rp).subtract(RistrettoPoint.BASE).toRawBytes();
|
|
180
|
+
RistrettoPoint.ZERO.equals(dp) === false;
|
|
181
|
+
// pre-hashed hash-to-curve
|
|
182
|
+
RistrettoPoint.hashToCurve(sha512(msg));
|
|
183
|
+
// full hash-to-curve including domain separation tag
|
|
184
|
+
hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });
|
|
158
185
|
```
|
|
159
186
|
|
|
160
|
-
#### ed448, X448
|
|
187
|
+
#### ed448, X448, decaf448
|
|
161
188
|
|
|
162
189
|
```ts
|
|
163
190
|
import { ed448 } from '@noble/curves/ed448';
|
|
@@ -167,17 +194,65 @@ const msg = new TextEncoder().encode('whatsup');
|
|
|
167
194
|
const sig = ed448.sign(msg, priv);
|
|
168
195
|
ed448.verify(sig, msg, pub);
|
|
169
196
|
|
|
170
|
-
|
|
197
|
+
// Variants from RFC8032: prehashed
|
|
198
|
+
import { ed448ph } from '@noble/curves/ed448';
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
ECDH using Curve448 aka X448, follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { x448 } from '@noble/curves/ed448';
|
|
171
205
|
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
|
|
172
206
|
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
|
|
207
|
+
|
|
208
|
+
// ed448 => x448 conversion
|
|
209
|
+
import { edwardsToMontgomeryPub } from '@noble/curves/ed448';
|
|
210
|
+
edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
decaf448 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { utf8ToBytes } from '@noble/hashes/utils';
|
|
217
|
+
import { shake256 } from '@noble/hashes/sha3';
|
|
218
|
+
import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';
|
|
219
|
+
|
|
220
|
+
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
|
|
221
|
+
hashToCurve(msg);
|
|
222
|
+
|
|
223
|
+
const dp = DecafPoint.fromHex(
|
|
224
|
+
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75'
|
|
225
|
+
);
|
|
226
|
+
DecafPoint.BASE.multiply(2n).add(dp).subtract(DecafPoint.BASE).toRawBytes();
|
|
227
|
+
DecafPoint.ZERO.equals(dp) === false;
|
|
228
|
+
// pre-hashed hash-to-curve
|
|
229
|
+
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
|
|
230
|
+
// full hash-to-curve including domain separation tag
|
|
231
|
+
hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });
|
|
173
232
|
```
|
|
174
233
|
|
|
175
|
-
Same RFC7748 / RFC8032 are followed.
|
|
234
|
+
Same RFC7748 / RFC8032 / IRTF draft are followed.
|
|
176
235
|
|
|
177
236
|
#### bls12-381
|
|
178
237
|
|
|
179
238
|
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
|
|
180
239
|
|
|
240
|
+
#### All available imports
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
|
244
|
+
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
|
|
245
|
+
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
|
246
|
+
import { p256 } from '@noble/curves/p256';
|
|
247
|
+
import { p384 } from '@noble/curves/p384';
|
|
248
|
+
import { p521 } from '@noble/curves/p521';
|
|
249
|
+
import { pallas, vesta } from '@noble/curves/pasta';
|
|
250
|
+
import { bls12_381 } from '@noble/curves/bls12-381';
|
|
251
|
+
import { bn254 } from '@noble/curves/bn254'; // also known as alt_bn128
|
|
252
|
+
import { jubjub } from '@noble/curves/jubjub';
|
|
253
|
+
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
|
|
254
|
+
```
|
|
255
|
+
|
|
181
256
|
#### Accessing a curve's variables
|
|
182
257
|
|
|
183
258
|
```ts
|
|
@@ -199,17 +274,6 @@ Precomputes are enabled for weierstrass and edwards BASE points of a curve. You
|
|
|
199
274
|
could precompute any other point (e.g. for ECDH) using `utils.precompute()`
|
|
200
275
|
method: check out examples.
|
|
201
276
|
|
|
202
|
-
There are following zero-dependency algorithms:
|
|
203
|
-
|
|
204
|
-
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
|
205
|
-
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
|
206
|
-
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
|
207
|
-
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
|
|
208
|
-
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
|
209
|
-
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
|
210
|
-
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
|
211
|
-
- [abstract/utils: General utilities](#abstractutils-general-utilities)
|
|
212
|
-
|
|
213
277
|
### abstract/weierstrass: Short Weierstrass curve
|
|
214
278
|
|
|
215
279
|
```ts
|
|
@@ -233,7 +297,7 @@ const secq256k1 = weierstrass({
|
|
|
233
297
|
randomBytes,
|
|
234
298
|
});
|
|
235
299
|
|
|
236
|
-
// Replace weierstrass with weierstrassPoints if you don't need ECDSA, hash, hmac, randomBytes
|
|
300
|
+
// Replace weierstrass() with weierstrassPoints() if you don't need ECDSA, hash, hmac, randomBytes
|
|
237
301
|
```
|
|
238
302
|
|
|
239
303
|
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
|
|
@@ -254,6 +318,11 @@ type CHash = {
|
|
|
254
318
|
};
|
|
255
319
|
```
|
|
256
320
|
|
|
321
|
+
**Message hash** is expected instead of message itself:
|
|
322
|
+
|
|
323
|
+
- `sign(msgHash, privKey)` is default behavior, assuming you pre-hash msg with sha2, or other hash
|
|
324
|
+
- `sign(msg, privKey, {prehash: true})` option can be used if you want to pass the message itself
|
|
325
|
+
|
|
257
326
|
**Weierstrass points:**
|
|
258
327
|
|
|
259
328
|
1. Exported as `ProjectivePoint`
|
|
@@ -349,6 +418,7 @@ More examples:
|
|
|
349
418
|
const priv = secq256k1.utils.randomPrivateKey();
|
|
350
419
|
secq256k1.getPublicKey(priv); // Convert private key to public.
|
|
351
420
|
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
|
|
421
|
+
const sig2 = secq256k1.sign(msg, priv, { prehash: true }); // hash(msg)
|
|
352
422
|
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
|
|
353
423
|
|
|
354
424
|
const Point = secq256k1.ProjectivePoint;
|
|
@@ -496,6 +566,8 @@ use aggregated, batch-verifiable
|
|
|
496
566
|
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
|
|
497
567
|
using Boneh-Lynn-Shacham signature scheme.
|
|
498
568
|
|
|
569
|
+
The module doesn't expose `CURVE` property: use `G1.CURVE`, `G2.CURVE` instead.
|
|
570
|
+
|
|
499
571
|
Main methods and properties are:
|
|
500
572
|
|
|
501
573
|
- `getPublicKey(privateKey)`
|
|
@@ -539,7 +611,12 @@ const aggSignature3 = bls.aggregateSignatures(signatures3);
|
|
|
539
611
|
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
|
|
540
612
|
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
|
|
541
613
|
|
|
542
|
-
//
|
|
614
|
+
// Pairings, with and without final exponentiation
|
|
615
|
+
// bls.pairing(PointG1, PointG2);
|
|
616
|
+
// bls.pairing(PointG1, PointG2, false);
|
|
617
|
+
// bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(eGS, ePHm));
|
|
618
|
+
|
|
619
|
+
// Others
|
|
543
620
|
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
|
|
544
621
|
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
|
|
545
622
|
|
|
@@ -598,7 +675,7 @@ utils: {
|
|
|
598
675
|
|
|
599
676
|
### abstract/hash-to-curve: Hashing strings to curve points
|
|
600
677
|
|
|
601
|
-
The module allows to hash arbitrary strings to elliptic curve points. Implements [
|
|
678
|
+
The module allows to hash arbitrary strings to elliptic curve points. Implements [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
|
|
602
679
|
|
|
603
680
|
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
|
|
604
681
|
|
|
@@ -614,19 +691,17 @@ bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
|
|
|
614
691
|
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
|
|
615
692
|
```
|
|
616
693
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
|
620
|
-
|
|
621
|
-
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
|
|
694
|
+
Low-level methods from the spec:
|
|
622
695
|
|
|
623
696
|
```ts
|
|
697
|
+
// produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
|
624
698
|
function expand_message_xmd(
|
|
625
699
|
msg: Uint8Array,
|
|
626
700
|
DST: Uint8Array,
|
|
627
701
|
lenInBytes: number,
|
|
628
|
-
H: CHash
|
|
702
|
+
H: CHash // For CHash see abstract/weierstrass docs section
|
|
629
703
|
): Uint8Array;
|
|
704
|
+
// produces a uniformly random byte string using an extendable-output function (XOF) H.
|
|
630
705
|
function expand_message_xof(
|
|
631
706
|
msg: Uint8Array,
|
|
632
707
|
DST: Uint8Array,
|
|
@@ -634,13 +709,9 @@ function expand_message_xof(
|
|
|
634
709
|
k: number,
|
|
635
710
|
H: CHash
|
|
636
711
|
): Uint8Array;
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
`hash_to_field(msg, count, options)`
|
|
640
|
-
[(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
|
|
641
|
-
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
|
|
712
|
+
// Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
|
713
|
+
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
|
|
642
714
|
|
|
643
|
-
```ts
|
|
644
715
|
/**
|
|
645
716
|
* * `DST` is a domain separation tag, defined in section 2.2.5
|
|
646
717
|
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
|
@@ -651,23 +722,13 @@ hashes arbitrary-length byte strings to a list of one or more elements of a fini
|
|
|
651
722
|
*/
|
|
652
723
|
type UnicodeOrBytes = string | Uint8Array;
|
|
653
724
|
type Opts = {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
725
|
+
DST: UnicodeOrBytes;
|
|
726
|
+
p: bigint;
|
|
727
|
+
m: number;
|
|
728
|
+
k: number;
|
|
729
|
+
expand?: 'xmd' | 'xof';
|
|
730
|
+
hash: CHash;
|
|
660
731
|
};
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
|
664
|
-
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
|
665
|
-
* @param msg a byte string containing the message to hash
|
|
666
|
-
* @param count the number of elements of F to output
|
|
667
|
-
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
|
668
|
-
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
|
669
|
-
*/
|
|
670
|
-
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
|
|
671
732
|
```
|
|
672
733
|
|
|
673
734
|
### abstract/poseidon: Poseidon hash
|
|
@@ -710,30 +771,40 @@ mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
|
|
|
710
771
|
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
|
|
711
772
|
```
|
|
712
773
|
|
|
713
|
-
|
|
774
|
+
Field operations are not constant-time: they are using JS bigints, see [security](#security).
|
|
775
|
+
The fact is mostly irrelevant, but the important method to keep in mind is `pow`,
|
|
776
|
+
which may leak exponent bits, when used naïvely.
|
|
714
777
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
778
|
+
`mod.Field` is always **field over prime**. Non-prime fields aren't supported for now.
|
|
779
|
+
We don't test for prime-ness for speed and because algorithms are probabilistic anyway.
|
|
780
|
+
Initializing a non-prime field could make your app suspectible to
|
|
781
|
+
DoS (infilite loop) on Tonelli-Shanks square root calculation.
|
|
718
782
|
|
|
719
|
-
|
|
783
|
+
Unlike `mod.invert`, `mod.invertBatch` won't throw on `0`: make sure to throw an error yourself.
|
|
784
|
+
|
|
785
|
+
#### Creating private keys from hashes
|
|
720
786
|
|
|
721
|
-
|
|
722
|
-
|
|
787
|
+
You can't simply make a 32-byte private key from a 32-byte hash.
|
|
788
|
+
Doing so will make the key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
|
|
723
789
|
|
|
724
|
-
|
|
725
|
-
|
|
790
|
+
To make the bias negligible, we follow [FIPS 186-5 A.2](https://csrc.nist.gov/publications/detail/fips/186/5/final)
|
|
791
|
+
and [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-5.2).
|
|
792
|
+
This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.
|
|
793
|
+
|
|
794
|
+
`hashToPrivateScalar()` that hashes to **private key** was created for this purpose.
|
|
795
|
+
Use [abstract/hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
|
796
|
+
if you need to hash to **public key**.
|
|
726
797
|
|
|
727
798
|
```ts
|
|
728
799
|
import { p256 } from '@noble/curves/p256';
|
|
729
800
|
import { sha256 } from '@noble/hashes/sha256';
|
|
730
801
|
import { hkdf } from '@noble/hashes/hkdf';
|
|
731
802
|
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
|
|
732
|
-
const derived = hkdf(sha256, someKey, undefined, 'application',
|
|
803
|
+
const derived = hkdf(sha256, someKey, undefined, 'application', 48); // 48 bytes for 32-byte priv
|
|
733
804
|
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
|
|
734
805
|
```
|
|
735
806
|
|
|
736
|
-
### abstract/utils:
|
|
807
|
+
### abstract/utils: Useful utilities
|
|
737
808
|
|
|
738
809
|
```ts
|
|
739
810
|
import * as utils from '@noble/curves/abstract/utils';
|
|
@@ -755,18 +826,36 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
|
|
755
826
|
|
|
756
827
|
## Security
|
|
757
828
|
|
|
758
|
-
1. The library has been audited
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
829
|
+
1. The library has been independently audited:
|
|
830
|
+
|
|
831
|
+
- in Feb 2023 by [Trail of Bits](https://www.trailofbits.com):
|
|
832
|
+
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
|
|
833
|
+
The audit has been funded by [Ryan Shea](https://www.shea.io).
|
|
834
|
+
Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`,
|
|
835
|
+
and top-level modules `_shortw_utils` and `secp256k1`.
|
|
836
|
+
See [changes since v0.7.3 audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
|
|
837
|
+
|
|
838
|
+
2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
|
|
839
|
+
You can run the fuzzer by yourself to check it.
|
|
840
|
+
3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations:
|
|
841
|
+
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
|
|
842
|
+
achieve in a scripting language. Which means _any other JS library can't have
|
|
843
|
+
constant-timeness_. Even statically typed Rust, a language without GC,
|
|
844
|
+
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
|
|
845
|
+
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
|
|
846
|
+
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
|
|
847
|
+
|
|
848
|
+
We consider infrastructure attacks like rogue NPM modules very important;
|
|
849
|
+
that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings.
|
|
850
|
+
If your app uses 500 dependencies, any dep could get hacked and you'll be
|
|
851
|
+
downloading malware with every `npm install`. Our goal is to minimize this attack vector.
|
|
852
|
+
As for devDependencies used by the library:
|
|
853
|
+
|
|
854
|
+
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing)
|
|
855
|
+
are developed by us and follow the same practices such as: minimal library size, auditability,
|
|
856
|
+
signed releases
|
|
857
|
+
- prettier (linter), fast-check (property-based testing), typescript versions
|
|
858
|
+
are locked and rarely updated. Every update is checked with `npm-diff`.
|
|
770
859
|
The packages are big, which makes it hard to audit their source code thoroughly and fully.
|
|
771
860
|
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
|
|
772
861
|
|
|
@@ -865,35 +954,37 @@ ed448 x 1,247 ops/sec @ 801μs/op
|
|
|
865
954
|
## Upgrading
|
|
866
955
|
|
|
867
956
|
Previously, the library was split into single-feature packages
|
|
868
|
-
noble-secp256k1
|
|
957
|
+
[noble-secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
|
958
|
+
[noble-ed25519](https://github.com/paulmillr/noble-ed25519) and
|
|
959
|
+
[noble-bls12-381](https://github.com/paulmillr/noble-bls12-381).
|
|
869
960
|
|
|
870
961
|
Curves continue their original work. The single-feature packages changed their
|
|
871
962
|
direction towards providing minimal 4kb implementations of cryptography,
|
|
872
963
|
which means they have less features.
|
|
873
964
|
|
|
874
|
-
Upgrading from
|
|
965
|
+
Upgrading from noble-secp256k1 2.0 or noble-ed25519 2.0: no changes, libraries are compatible.
|
|
875
966
|
|
|
876
|
-
Upgrading from
|
|
967
|
+
Upgrading from noble-secp256k1 1.7:
|
|
877
968
|
|
|
878
969
|
- `getPublicKey`
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
970
|
+
- now produce 33-byte compressed signatures by default
|
|
971
|
+
- to use old behavior, which produced 65-byte uncompressed keys, set
|
|
972
|
+
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
|
|
882
973
|
- `sign`
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
974
|
+
- is now sync; use `signAsync` for async version
|
|
975
|
+
- now returns `Signature` instance with `{ r, s, recovery }` properties
|
|
976
|
+
- `canonical` option was renamed to `lowS`
|
|
977
|
+
- `recovered` option has been removed because recovery bit is always returned now
|
|
978
|
+
- `der` option has been removed. There are 2 options:
|
|
979
|
+
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
|
|
980
|
+
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
|
|
981
|
+
2. If you must use DER encoding, switch to noble-curves (see above).
|
|
891
982
|
- `verify`
|
|
892
|
-
|
|
983
|
+
- `strict` option was renamed to `lowS`
|
|
893
984
|
- `getSharedSecret`
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
985
|
+
- now produce 33-byte compressed signatures by default
|
|
986
|
+
- to use old behavior, which produced 65-byte uncompressed keys, set
|
|
987
|
+
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
|
|
897
988
|
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
|
|
898
989
|
- `number` type for private keys have been removed: use `bigint` instead
|
|
899
990
|
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
|
|
@@ -914,53 +1005,65 @@ Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
|
|
|
914
1005
|
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
|
|
915
1006
|
|
|
916
1007
|
- Methods and classes were renamed:
|
|
917
|
-
|
|
918
|
-
|
|
1008
|
+
- PointG1 -> G1.Point, PointG2 -> G2.Point
|
|
1009
|
+
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
|
|
919
1010
|
- Fp2 ORDER was corrected
|
|
920
1011
|
|
|
921
1012
|
## Resources
|
|
922
1013
|
|
|
923
|
-
Useful documentation and articles about the library or its primitives:
|
|
924
|
-
|
|
925
1014
|
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
|
|
926
|
-
-
|
|
927
|
-
|
|
1015
|
+
- EdDSA
|
|
1016
|
+
- [A Deep dive into Ed25519 Signatures](https://cendyne.dev/posts/2022-03-06-ed25519-signatures.html)
|
|
1017
|
+
- [Ed25519 Deep Dive Addendum](https://cendyne.dev/posts/2022-09-11-ed25519-deep-dive-addendum.html)
|
|
1018
|
+
- [It’s 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am)
|
|
1019
|
+
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
|
|
1020
|
+
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
|
|
1021
|
+
- [Cofactor Explained: Clearing Elliptic Curves’ dirty little secret](https://loup-vaillant.fr/tutorials/cofactor)
|
|
1022
|
+
- [Surrounded by Elligators](https://loup-vaillant.fr/articles/implementing-elligator)
|
|
928
1023
|
- Pairings and BLS
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1024
|
+
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
|
|
1025
|
+
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
|
|
1026
|
+
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
|
|
1027
|
+
- Pairing over bls12-381:
|
|
1028
|
+
[fields](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
|
|
1029
|
+
[curves](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
|
|
1030
|
+
[pairings](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
|
|
1031
|
+
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
|
|
937
1032
|
|
|
938
|
-
|
|
1033
|
+
### Demos
|
|
939
1034
|
|
|
940
1035
|
- [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
|
|
941
1036
|
- [BLS threshold signatures](https://genthresh.com)
|
|
942
1037
|
|
|
943
|
-
Projects using
|
|
1038
|
+
### Projects using curves
|
|
944
1039
|
|
|
945
|
-
- [scure-bip32](https://github.com/paulmillr/scure-bip32)
|
|
1040
|
+
- HDkey libraries: [scure-bip32](https://github.com/paulmillr/scure-bip32), [bip32](https://github.com/bitcoinjs/bip32)
|
|
1041
|
+
- Social networks: [nostr](https://github.com/nbd-wtf/nostr-tools), [bluesky](https://github.com/bluesky-social/atproto)
|
|
946
1042
|
- Ethereum libraries:
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
- Bitcoin libraries:
|
|
1043
|
+
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
|
|
1044
|
+
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer),
|
|
1045
|
+
[ethers](https://github.com/ethers-io/ethers.js) (old noble),
|
|
1046
|
+
[viem.sh](https://viem.sh),
|
|
1047
|
+
[@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
|
|
1048
|
+
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
|
|
1049
|
+
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
|
|
1050
|
+
- Bitcoin libraries:
|
|
1051
|
+
- [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
|
|
1052
|
+
- [tapscript](https://github.com/cmdruid/tapscript)
|
|
955
1053
|
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
|
|
956
|
-
-
|
|
957
|
-
- [
|
|
958
|
-
- [
|
|
1054
|
+
- Other web3 stuff:
|
|
1055
|
+
- [scure-starknet](https://github.com/paulmillr/scure-starknet)
|
|
1056
|
+
- [aztec](https://github.com/AztecProtocol/aztec-packages)
|
|
1057
|
+
- [polkadot.js](https://github.com/polkadot-js/common), [drand-client](https://github.com/drand/drand-client), [moneroj](https://github.com/beritani/moneroj), [tronlib](https://github.com/CoinSpace/tronlib)
|
|
1058
|
+
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble for now)
|
|
1059
|
+
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js),
|
|
1060
|
+
[js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise)
|
|
959
1061
|
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
|
|
960
1062
|
- [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
|
|
961
|
-
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
|
|
1063
|
+
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
|
|
962
1064
|
- [BLS BBS signatures](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
|
|
963
1065
|
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
|
|
1066
|
+
- See [full list of projects on GitHub](https://github.com/paulmillr/noble-curves/network/dependents).
|
|
964
1067
|
|
|
965
1068
|
## License
|
|
966
1069
|
|