@noble/post-quantum 0.6.1 โ 0.7.1
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 +191 -104
- package/_crystals.d.ts +8 -3
- package/_crystals.js +38 -10
- package/falcon.d.ts +1 -2
- package/falcon.js +202 -115
- package/hybrid.d.ts +38 -28
- package/hybrid.js +215 -86
- package/index.d.ts +0 -1
- package/index.js +1 -2
- package/ml-dsa.d.ts +31 -5
- package/ml-dsa.js +118 -34
- package/ml-kem.d.ts +45 -4
- package/ml-kem.js +206 -64
- package/package.json +16 -20
- package/slh-dsa.d.ts +23 -3
- package/slh-dsa.js +127 -60
- package/src/_crystals.ts +45 -11
- package/src/falcon.ts +206 -121
- package/src/hybrid.ts +217 -83
- package/src/index.ts +1 -1
- package/src/ml-dsa.ts +140 -43
- package/src/ml-kem.ts +239 -66
- package/src/slh-dsa.ts +149 -69
- package/src/utils.ts +186 -25
- package/src/webcrypto.ts +322 -0
- package/utils.d.ts +54 -4
- package/utils.js +167 -28
- package/webcrypto.d.ts +91 -0
- package/webcrypto.js +213 -0
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,22 +3,20 @@
|
|
|
3
3
|
Auditable & minimal JS implementation of post-quantum public-key cryptography.
|
|
4
4
|
|
|
5
5
|
- ๐ Auditable
|
|
6
|
-
-
|
|
7
|
-
-
|
|
6
|
+
- ๐ชถ Minimal: 7KB (gzipped) ML-KEM, unused code is excluded from your builds
|
|
7
|
+
- ๐ Fast: hand-optimized for caveats of JS engines
|
|
8
|
+
- ๐ Reliable: ACVP / wycheproof tests ensure correctness
|
|
8
9
|
- ๐ฆพ ML-KEM & CRYSTALS-Kyber: lattice-based KEM from FIPS-203
|
|
9
10
|
- ๐ ML-DSA & CRYSTALS-Dilithium: lattice-based signatures from FIPS-204
|
|
10
11
|
- ๐ SLH-DSA & SPHINCS+: hash-based Winternitz signatures from FIPS-205
|
|
11
12
|
- ๐ฆ
Falcon: lattice-based signatures from Falcon Round 3
|
|
12
|
-
- ๐ก Hybrid algorithms
|
|
13
|
-
- ๐ชถ 16KB (gzipped) for everything, including bundled hashes & curves
|
|
14
|
-
|
|
15
|
-
Take a glance at [GitHub Discussions](https://github.com/paulmillr/noble-post-quantum/discussions) for questions and support.
|
|
13
|
+
- ๐ก Hybrid algorithms (combining classic & post-quantum)
|
|
16
14
|
|
|
17
15
|
> [!IMPORTANT]
|
|
18
|
-
> NIST published [IR 8547](https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf),
|
|
19
|
-
> prohibiting classical cryptography (RSA, DSA, ECDSA, ECDH) after 2035.
|
|
20
|
-
>
|
|
21
|
-
> Take
|
|
16
|
+
> NIST published draft [IR 8547](https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf),
|
|
17
|
+
> which proposes prohibiting classical cryptography (RSA, DSA, ECDSA, ECDH) after 2035.
|
|
18
|
+
> Australia's ASD does the same [after 2030](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography).
|
|
19
|
+
> Take this into account when designing new cryptographic systems.
|
|
22
20
|
|
|
23
21
|
### This library belongs to _noble_ cryptography
|
|
24
22
|
|
|
@@ -34,6 +32,7 @@ Take a glance at [GitHub Discussions](https://github.com/paulmillr/noble-post-qu
|
|
|
34
32
|
[post-quantum](https://github.com/paulmillr/noble-post-quantum),
|
|
35
33
|
5kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
|
|
36
34
|
[ed25519](https://github.com/paulmillr/noble-ed25519)
|
|
35
|
+
- WASM version: [awasm-noble](https://github.com/paulmillr/awasm-noble)
|
|
37
36
|
- [Check out the homepage](https://paulmillr.com/noble/)
|
|
38
37
|
for reading resources, documentation, and apps built with noble
|
|
39
38
|
|
|
@@ -72,8 +71,7 @@ import {
|
|
|
72
71
|
} from '@noble/post-quantum/falcon.js';
|
|
73
72
|
import {
|
|
74
73
|
ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
|
|
75
|
-
KitchenSink_ml_kem768_x25519,
|
|
76
|
-
QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
|
|
74
|
+
KitchenSink_ml_kem768_x25519, QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
|
|
77
75
|
} from '@noble/post-quantum/hybrid.js';
|
|
78
76
|
```
|
|
79
77
|
|
|
@@ -81,19 +79,18 @@ import {
|
|
|
81
79
|
- [ML-DSA / Dilithium](#ml-dsa--dilithium-signatures)
|
|
82
80
|
- [SLH-DSA / SPHINCS+](#slh-dsa--sphincs-signatures)
|
|
83
81
|
- [Falcon](#falcon-signatures)
|
|
84
|
-
- [hybrid:
|
|
82
|
+
- [hybrid: X-Wing, KitchenSink and others](#hybrid-x-wing-kitchensink-and-others)
|
|
85
83
|
- [What should I use?](#what-should-i-use)
|
|
86
84
|
- [Security](#security)
|
|
87
|
-
- [Speed](#speed)
|
|
88
85
|
- [Contributing & testing](#contributing--testing)
|
|
86
|
+
- [Speed](#speed)
|
|
89
87
|
- [License](#license)
|
|
90
88
|
|
|
91
89
|
### ML-KEM / Kyber shared secrets
|
|
92
90
|
|
|
93
91
|
```ts
|
|
94
92
|
import { ml_kem512, ml_kem768, ml_kem1024 } from '@noble/post-quantum/ml-kem.js';
|
|
95
|
-
import { randomBytes } from '@noble/post-quantum/utils.js';
|
|
96
|
-
import { notDeepStrictEqual } from 'node:assert';
|
|
93
|
+
import { equalBytes, randomBytes } from '@noble/post-quantum/utils.js';
|
|
97
94
|
const seed = randomBytes(64); // seed is optional
|
|
98
95
|
const aliceKeys = ml_kem768.keygen(seed);
|
|
99
96
|
const { cipherText, sharedSecret: bobShared } = ml_kem768.encapsulate(aliceKeys.publicKey);
|
|
@@ -102,7 +99,7 @@ const aliceShared = ml_kem768.decapsulate(cipherText, aliceKeys.secretKey);
|
|
|
102
99
|
// Warning: Can be MITM-ed
|
|
103
100
|
const malloryKeys = ml_kem768.keygen();
|
|
104
101
|
const malloryShared = ml_kem768.decapsulate(cipherText, malloryKeys.secretKey); // No error!
|
|
105
|
-
|
|
102
|
+
console.log(equalBytes(aliceShared, malloryShared)); // false: different key!
|
|
106
103
|
```
|
|
107
104
|
|
|
108
105
|
Lattice-based key encapsulation mechanism, defined in [FIPS-203](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf) ([website](https://www.pq-crystals.org/kyber/resources.shtml), [repo](https://github.com/pq-crystals/kyber)).
|
|
@@ -126,6 +123,25 @@ Old, incompatible version (Kyber) is not provided. Open an issue if you need it.
|
|
|
126
123
|
> `decapsulate` will simply return a different shared secret.
|
|
127
124
|
> ML-KEM is also probabilistic and relies on quality of CSPRNG.
|
|
128
125
|
|
|
126
|
+
#### webcrypto: friendly wrapper
|
|
127
|
+
|
|
128
|
+
WebCrypto-backed ML-KEM and `ml_kem768_x25519` wrappers are also available. Their methods are async
|
|
129
|
+
and require a runtime that implements the corresponding experimental WebCrypto API.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { ml_kem768 } from '@noble/post-quantum/webcrypto.js';
|
|
133
|
+
|
|
134
|
+
if (await ml_kem768.isSupported()) {
|
|
135
|
+
const aliceKeys = await ml_kem768.keygen();
|
|
136
|
+
const { cipherText, sharedSecret: bobShared } = await ml_kem768.encapsulate(aliceKeys.publicKey);
|
|
137
|
+
const aliceShared = await ml_kem768.decapsulate(cipherText, aliceKeys.secretKey);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The ML-KEM wrappers serialize private keys as 64-byte `raw-seed` values; the X25519 hybrid uses a
|
|
142
|
+
32-byte seed. They can be passed to the corresponding synchronous implementation's `keygen(seed)`,
|
|
143
|
+
but are not expanded decapsulation keys.
|
|
144
|
+
|
|
129
145
|
### ML-DSA / Dilithium signatures
|
|
130
146
|
|
|
131
147
|
```ts
|
|
@@ -142,6 +158,35 @@ Lattice-based digital signature algorithm, defined in [FIPS-204](https://nvlpubs
|
|
|
142
158
|
[repo](https://github.com/pq-crystals/dilithium)).
|
|
143
159
|
The internals are similar to ML-KEM, but keys and params are different.
|
|
144
160
|
|
|
161
|
+
`sign` / `verify` accept optional parameters:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
165
|
+
import { sha512 } from '@noble/hashes/sha2.js';
|
|
166
|
+
const keys = ml_dsa65.keygen();
|
|
167
|
+
const msg = new TextEncoder().encode('hello noble');
|
|
168
|
+
const ctx = new Uint8Array([1, 2, 3]);
|
|
169
|
+
const sigCtx = ml_dsa65.sign(msg, keys.secretKey, { context: ctx }); // verify needs same context
|
|
170
|
+
const sigDet = ml_dsa65.sign(msg, keys.secretKey, { extraEntropy: false }); // deterministic
|
|
171
|
+
const hml = ml_dsa65.prehash(sha512); // HashML-DSA
|
|
172
|
+
const sigPre = hml.sign(msg, keys.secretKey);
|
|
173
|
+
const isValidPre = hml.verify(sigPre, msg, keys.publicKey);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- `context`: domain-separation byte string, up to 255 bytes; must match between `sign` and `verify`
|
|
177
|
+
- `extraEntropy`: hedged-signing randomness. Default is 32 random bytes;
|
|
178
|
+
`false` produces deterministic signatures; custom 32-byte value is also allowed
|
|
179
|
+
- `prehash(hash)`: pre-hash variant (HashML-DSA) from FIPS-204
|
|
180
|
+
|
|
181
|
+
Unknown option keys are rejected rather than ignored, so a misspelling such as
|
|
182
|
+
`{ ctx }` fails loudly instead of silently signing with no domain separation.
|
|
183
|
+
|
|
184
|
+
`externalMu`, which treats `msg` as the precomputed 64-byte message representative
|
|
185
|
+
ยต, is available on `ml_dsa*.internal.sign` / `internal.verify` only. The public
|
|
186
|
+
wrappers reject it: `sign` formats `M'` before the 64-byte check so it could never
|
|
187
|
+
accept a ยต, and `verify` did not forward it, returning `false` for a valid
|
|
188
|
+
external-mu signature.
|
|
189
|
+
|
|
145
190
|
### SLH-DSA / SPHINCS+ signatures
|
|
146
191
|
|
|
147
192
|
```ts
|
|
@@ -172,6 +217,9 @@ Hash-based digital signature algorithm, defined in [FIPS-205](https://nvlpubs.ni
|
|
|
172
217
|
- 128 / 192 / 256: indicates security level in bits
|
|
173
218
|
- s / f: indicates small vs fast trade-off
|
|
174
219
|
|
|
220
|
+
`sign` / `verify` accept the same optional `context`, `extraEntropy` and `prehash(hash)`
|
|
221
|
+
(HashSLH-DSA) parameters as ML-DSA. With `extraEntropy: false`, signing is deterministic.
|
|
222
|
+
|
|
175
223
|
SLH-DSA is slow: see [benchmarks](#speed) for key size & speed.
|
|
176
224
|
|
|
177
225
|
### Falcon signatures
|
|
@@ -197,53 +245,87 @@ Lattice-based digital signature algorithm, submitted to NIST PQC Round 3 ([websi
|
|
|
197
245
|
- `falcon512padded`, `falcon1024padded`: fixed-length detached signatures
|
|
198
246
|
- `attached.seal(...)` / `attached.open(...)`: attached-signature API for Round 3 vectors and interop
|
|
199
247
|
|
|
200
|
-
|
|
248
|
+
> [!WARNING]
|
|
249
|
+
> Falcon signing is randomized by design. Leave signing options unset in production so every
|
|
250
|
+
> signature receives a fresh 40-byte public nonce and a fresh 48-byte sampler seed from the system
|
|
251
|
+
> CSPRNG. Falcon's `extraEntropy` option does not have the hedged semantics used by ML-DSA and
|
|
252
|
+
> SLH-DSA:
|
|
253
|
+
>
|
|
254
|
+
> - `extraEntropy: false` seeds an AES-CTR-DRBG with 48 zero bytes. It makes signatures
|
|
255
|
+
> deterministic for a fixed key and message, and reuses the same nonce and initial random stream
|
|
256
|
+
> across different messages. This is outside the Falcon Round 3 randomized-hash design.
|
|
257
|
+
> - A 48-byte `extraEntropy` value replaces system randomness; it is not mixed with fresh entropy.
|
|
258
|
+
> Reusing a value therefore reuses the signing stream.
|
|
259
|
+
> - The raw `random` callback overrides `extraEntropy` and supplies both the nonce and sampler seed.
|
|
260
|
+
> It exists for test-vector reproduction and should not be used as a production randomness hook.
|
|
261
|
+
>
|
|
262
|
+
> In particular, do not copy ML-DSA examples that use `extraEntropy: false` into Falcon code.
|
|
263
|
+
|
|
264
|
+
`attached.open(...)` throws when verification fails and returns a fresh copy of the embedded
|
|
265
|
+
message when it succeeds. The result does not alias the attached signature or public-key buffers.
|
|
266
|
+
Detached `verify(...)` returns `false` for an invalid signature.
|
|
267
|
+
|
|
268
|
+
### hybrid: X-Wing, KitchenSink and others
|
|
201
269
|
|
|
202
270
|
```js
|
|
203
271
|
import {
|
|
204
272
|
ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
|
|
205
|
-
KitchenSink_ml_kem768_x25519,
|
|
273
|
+
KitchenSink_ml_kem768_x25519,
|
|
206
274
|
QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
|
|
207
275
|
} from '@noble/post-quantum/hybrid.js';
|
|
208
276
|
```
|
|
209
277
|
|
|
210
|
-
|
|
278
|
+
The hybrid submodule combines post-quantum algorithms with elliptic curve cryptography:
|
|
211
279
|
|
|
212
|
-
- `ml_kem768_x25519`: ML-KEM-768 + X25519
|
|
213
|
-
|
|
214
|
-
- `
|
|
280
|
+
- `ml_kem768_x25519`: ML-KEM-768 + X25519, implementing X-Wing under the descriptive
|
|
281
|
+
`ml_kem768_x25519` export name. There is no separate `XWing` alias.
|
|
282
|
+
- `ml_kem768_p256`: ML-KEM-768 + P-256 using the current CG framework construction
|
|
283
|
+
- `ml_kem1024_p384`: ML-KEM-1024 + P-384 using the current CG framework construction
|
|
215
284
|
- `KitchenSink_ml_kem768_x25519`: ML-KEM-768 + X25519 with HKDF-SHA256 combiner
|
|
216
|
-
- `QSF_ml_kem768_p256`:
|
|
217
|
-
|
|
285
|
+
- `QSF_ml_kem768_p256`, `QSF_ml_kem1024_p384`: legacy compatibility presets for the older
|
|
286
|
+
QSF/C2PRI naming and labels. New code should use `ml_kem768_p256` and `ml_kem1024_p384`.
|
|
218
287
|
|
|
219
|
-
|
|
288
|
+
> **Security note:** `_ecdhKem(curve)` is an internal raw-ECDH component adapter, not a standalone
|
|
289
|
+
> IND-CCA-secure KEM. It has no KDF and does not bind the encapsulation or recipient public key, so
|
|
290
|
+
> different accepted point encodings can derive the same bytes. Use it only within a specified
|
|
291
|
+
> combiner that performs that binding, or use a standardized DHKEM. The built-in hybrid presets
|
|
292
|
+
> retain their specified combiners and test-vector-compatible behavior.
|
|
220
293
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
- [
|
|
224
|
-
- [
|
|
294
|
+
The current `ml_kem*` presets are tested against these work-in-progress specifications:
|
|
295
|
+
|
|
296
|
+
- [irtf-cfrg-hybrid-kems-12](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hybrid-kems-12)
|
|
297
|
+
- [irtf-cfrg-concrete-hybrid-kems-03](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-concrete-hybrid-kems-03)
|
|
298
|
+
- [connolly-cfrg-xwing-kem-10](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem-10)
|
|
299
|
+
|
|
300
|
+
`QSF(...)` is the legacy API name for the construction now called the C2PRI combiner. It derives
|
|
301
|
+
the final secret from `ssPQ || ssT || ctT || ekT || label`; omitting the PQ ciphertext and
|
|
302
|
+
encapsulation key is intentional and relies on the PQ KEM's C2PRI property. The `QSF_*` presets
|
|
303
|
+
retain older draft labels and vectors for compatibility, so they do not implement the current
|
|
304
|
+
concrete preset encodings. They are also unrelated to the separate universal-combiner example in
|
|
305
|
+
NIST SP 800-227.
|
|
225
306
|
|
|
226
307
|
### What should I use?
|
|
227
308
|
|
|
228
|
-
| | Speed | Key size | Sig size
|
|
229
|
-
| ------- | ------ | ----------- |
|
|
230
|
-
| RSA | Normal | 256B - 2KB | 256B - 2KB
|
|
231
|
-
| ECC | Normal | 32 - 256B | 48 - 128B
|
|
232
|
-
| ML-KEM | Fast |
|
|
233
|
-
| ML-DSA | Normal | 1.3 - 2.5KB | 2.5 - 4.5KB
|
|
234
|
-
| SLH-DSA | Slow | 32 - 128B | 17 - 50KB
|
|
235
|
-
| FN-DSA | Slow | 0.9 - 1.8KB | 0.6 - 1.2KB
|
|
309
|
+
| | Speed | Key size | Sig / CT size | Created in | Popularized in | Post-quantum? |
|
|
310
|
+
| ------- | ------ | ----------- | ------------- | ---------- | -------------- | ------------- |
|
|
311
|
+
| RSA | Normal | 256B - 2KB | 256B - 2KB | 1970s | 1990s | No |
|
|
312
|
+
| ECC | Normal | 32 - 256B | 48 - 128B | 1980s | 2010s | No |
|
|
313
|
+
| ML-KEM | Fast | 0.8 - 1.6KB | 0.8 - 1.6KB | 1990s | 2020s | Yes |
|
|
314
|
+
| ML-DSA | Normal | 1.3 - 2.5KB | 2.5 - 4.5KB | 1990s | 2020s | Yes |
|
|
315
|
+
| SLH-DSA | Slow | 32 - 128B | 17 - 50KB | 1970s | 2020s | Yes |
|
|
316
|
+
| FN-DSA | Slow | 0.9 - 1.8KB | 0.6 - 1.2KB | 1990s | 2020s | Yes |
|
|
236
317
|
|
|
237
|
-
|
|
318
|
+
ML-KEM is a KEM, not a signature scheme: its last column is ciphertext (CT) size.
|
|
319
|
+
We suggest using ECC + ML-KEM for key agreement, ECC + SLH-DSA for signatures.
|
|
238
320
|
|
|
239
321
|
ML-KEM and ML-DSA are lattice-based. SLH-DSA is hash-based, which means it is built on top of older, more conservative primitives. NIST guidance for security levels:
|
|
240
322
|
|
|
241
323
|
- Category 3 (~AES-192): ML-KEM-768, ML-DSA-65, SLH-DSA-192
|
|
242
324
|
- Category 5 (~AES-256): ML-KEM-1024, ML-DSA-87, SLH-DSA-256
|
|
243
325
|
|
|
244
|
-
NIST recommends
|
|
326
|
+
NIST recommends cat-3+, while Australian [ASD only allows cat-5 after 2030](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography).
|
|
245
327
|
|
|
246
|
-
It's also useful to check out [NIST SP 800-131Ar3](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar3.ipd.pdf)
|
|
328
|
+
It's also useful to check out draft [NIST SP 800-131Ar3](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar3.ipd.pdf)
|
|
247
329
|
for "Transitioning the Use of Cryptographic Algorithms and Key Lengths".
|
|
248
330
|
|
|
249
331
|
For [hashes](https://github.com/paulmillr/noble-hashes), use SHA512 or SHA3-512 (not SHA256); and for [ciphers](https://github.com/paulmillr/noble-ciphers) ensure AES-256 or ChaCha.
|
|
@@ -260,9 +342,23 @@ If you see anything unusual: investigate and report.
|
|
|
260
342
|
|
|
261
343
|
### Constant-timeness
|
|
262
344
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
345
|
+
This pure JavaScript implementation does not claim constant-time execution. JavaScript engines,
|
|
346
|
+
JIT compilers, garbage collection, floating-point operations and `bigint` arithmetic do not offer
|
|
347
|
+
the execution guarantees needed for a formal constant-time claim.
|
|
348
|
+
|
|
349
|
+
- ML-DSA signing uses rejection loops, early-exit norm checks and conditional arithmetic whose
|
|
350
|
+
execution depends on secret-key and per-signature state. Fresh randomized signing is the default,
|
|
351
|
+
but it does not turn the implementation into a constant-time one.
|
|
352
|
+
- Falcon signing uses data-dependent Gaussian and rejection sampling, floating-point operations,
|
|
353
|
+
and `bigint` paths. Its timing and microarchitectural side-channel posture is materially weaker
|
|
354
|
+
than a hardened native implementation. Deterministic or repeated signing randomness can make
|
|
355
|
+
observations easier to correlate and should be avoided.
|
|
356
|
+
- These limitations matter most when an attacker can measure signing closely, such as hostile
|
|
357
|
+
co-tenancy, shared hardware, or a high-resolution local timing oracle. Use an isolated execution
|
|
358
|
+
environment or a reviewed native/constant-time backend when that is part of the threat model.
|
|
359
|
+
|
|
360
|
+
We actively research how to improve this property for post-quantum algorithms in JS. Even hardware
|
|
361
|
+
ML-KEM implementations require careful side-channel engineering and [have had practical attacks](https://eprint.iacr.org/2023/1084).
|
|
266
362
|
|
|
267
363
|
### Supply chain security
|
|
268
364
|
|
|
@@ -274,10 +370,11 @@ Keep in mind that even hardware versions ML-KEM [are vulnerable](https://eprint.
|
|
|
274
370
|
- Version ranges are locked, and changes are checked with npm-diff.
|
|
275
371
|
- **Dev dependencies** are excluded from end-user installs; they're only used for development and build steps.
|
|
276
372
|
|
|
277
|
-
For this package, there are
|
|
373
|
+
For this package, there are 3 dependencies; and a few dev dependencies:
|
|
278
374
|
|
|
279
375
|
- [noble-hashes](https://github.com/paulmillr/noble-hashes) provides cryptographic hashing functionality, used internally in every algorithm
|
|
280
376
|
- [noble-curves](https://github.com/paulmillr/noble-curves) provides elliptic curve cryptography for hybrid algorithms
|
|
377
|
+
- [noble-ciphers](https://github.com/paulmillr/noble-ciphers) provides AES-CTR DRBG and ChaCha20, used internally in Falcon
|
|
281
378
|
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
|
|
282
379
|
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation
|
|
283
380
|
|
|
@@ -289,73 +386,63 @@ which is considered a cryptographically secure PRNG.
|
|
|
289
386
|
|
|
290
387
|
Browsers have had weaknesses in the past - and could again - but implementing a userspace CSPRNG is even worse, as thereโs no reliable userspace source of high-quality entropy.
|
|
291
388
|
|
|
292
|
-
## Contributing & testing
|
|
293
|
-
|
|
294
|
-
- `npm install && npm run build && npm test` will build the code and run tests.
|
|
295
|
-
- `npm run lint` / `npm run format` will run linter / fix linter issues.
|
|
296
|
-
- `npm run bench` will run benchmarks
|
|
297
|
-
- `npm run build:release` will build single file
|
|
298
|
-
|
|
299
|
-
Check out [github.com/paulmillr/guidelines](https://github.com/paulmillr/guidelines)
|
|
300
|
-
for general coding practices and rules.
|
|
301
|
-
|
|
302
|
-
See [paulmillr.com/noble](https://paulmillr.com/noble/)
|
|
303
|
-
for useful resources, articles, documentation and demos
|
|
304
|
-
related to the library.
|
|
305
|
-
|
|
306
389
|
## Speed
|
|
307
390
|
|
|
308
|
-
> `npm run
|
|
391
|
+
> `npm run benchmark`
|
|
309
392
|
|
|
310
393
|
Noble is the fastest JS implementation of post-quantum algorithms.
|
|
311
394
|
|
|
312
|
-
|
|
395
|
+
There is experimental [git branch](https://github.com/paulmillr/noble-post-quantum/tree/awasm),
|
|
396
|
+
which uses WASM-based [awasm-noble](https://github.com/paulmillr/awasm-noble) for hashing.
|
|
397
|
+
It has 80% faster ML-KEM, 30% faster ML-DSA, 2.3x faster SLH-DSA-SHA256, 15x faster SLH-DSA-SHAKE.
|
|
398
|
+
Try it out.
|
|
313
399
|
|
|
314
|
-
|
|
315
|
-
# ML-KEM768
|
|
316
|
-
keygen x 4,277 ops/sec @ 233ฮผs/op
|
|
317
|
-
encapsulate x 3,470 ops/sec @ 288ฮผs/op
|
|
318
|
-
decapsulate x 3,757 ops/sec @ 266ฮผs/op
|
|
319
|
-
# ML-DSA65
|
|
320
|
-
keygen x 669 ops/sec @ 1ms/op
|
|
321
|
-
sign x 271 ops/sec @ 3ms/op
|
|
322
|
-
verify x 565 ops/sec @ 1ms/op
|
|
323
|
-
# SLH-DSA SHA2 192f
|
|
324
|
-
keygen x 235 ops/sec @ 4ms/op
|
|
325
|
-
sign x 8 ops/sec @ 117ms/op
|
|
326
|
-
verify x 159 ops/sec @ 6ms/op
|
|
327
|
-
# Falcon512
|
|
328
|
-
keygen x 14 ops/sec @ 66ms/op ยฑ 11.01% (56ms..96ms)
|
|
329
|
-
sign x 749 ops/sec @ 1ms/op
|
|
330
|
-
verify x 2,160 ops/sec @ 462ฮผs/op
|
|
331
|
-
# Falcon1024
|
|
332
|
-
keygen x 4 ops/sec @ 247ms/op ยฑ 5.22% (234ms..266ms)
|
|
333
|
-
sign x 343 ops/sec @ 2ms/op
|
|
334
|
-
verify x 950 ops/sec @ 1ms/op
|
|
335
|
-
```
|
|
400
|
+
Benchmarks on Apple M4 (operations/sec, **higher is better**):
|
|
336
401
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
| OPs/sec | Keygen | Signing | Verification | Shared secret |
|
|
402
|
+
| Primitive | Keygen | Signing | Verification | Shared secret |
|
|
340
403
|
| ----------------- | ------ | ------- | ------------ | ------------- |
|
|
341
|
-
|
|
|
342
|
-
| ML-
|
|
343
|
-
|
|
|
344
|
-
| SLH-DSA-SHA2-192f |
|
|
345
|
-
|
|
|
346
|
-
|
|
347
|
-
SLH-DSA:
|
|
348
|
-
|
|
349
|
-
|
|
|
350
|
-
|
|
|
351
|
-
| sha2_128f
|
|
352
|
-
|
|
|
353
|
-
|
|
|
354
|
-
|
|
|
355
|
-
|
|
|
356
|
-
|
|
|
357
|
-
|
|
|
358
|
-
|
|
|
404
|
+
| ML-KEM-768 | 4661 | | | 4089 |
|
|
405
|
+
| ML-DSA-65 | 719 | 294 | 610 | |
|
|
406
|
+
| Falcon512 | 14 | 749 | 2160 | |
|
|
407
|
+
| SLH-DSA-SHA2-192f | 321 | 11 | 198 | |
|
|
408
|
+
| Pre-quantum x/ed25519 | 12648 | 6157 | 1255 | 1981 |
|
|
409
|
+
|
|
410
|
+
SLH-DSA (`s` variants have 2x shorter signatures; SHAKE is very slow):
|
|
411
|
+
|
|
412
|
+
| | keygen | sign | verify |
|
|
413
|
+
| ---------- | ------ | ------ | ------ |
|
|
414
|
+
| sha2_128f | 2ms | 47ms | 3ms |
|
|
415
|
+
| shake_128f | 10ms | 237ms | 14ms |
|
|
416
|
+
| sha2_192f | 3.2ms | 93ms | 5.1ms |
|
|
417
|
+
| shake_192f | 15ms | 396ms | 21ms |
|
|
418
|
+
| sha2_256f | 8.5ms | 187ms | 5.2ms |
|
|
419
|
+
| shake_256f | 40ms | 813ms | 22ms |
|
|
420
|
+
| sha2_128s | 140ms | 1068ms | 1.1ms |
|
|
421
|
+
| shake_128s | 673ms | 5114ms | 5.2ms |
|
|
422
|
+
| sha2_192s | 209ms | 2114ms | 1.9ms |
|
|
423
|
+
| shake_192s | 974ms | 8779ms | 7.1ms |
|
|
424
|
+
| sha2_256s | 137ms | 1941ms | 2.7ms |
|
|
425
|
+
| shake_256s | 645ms | 7689ms | 11ms |
|
|
426
|
+
|
|
427
|
+
Key and signature sizes:
|
|
428
|
+
|
|
429
|
+
| Variant | Public key | Secret key | Signature / Ciphertext |
|
|
430
|
+
|---|---:|---:|---:|
|
|
431
|
+
| ML-KEM-512 | 800 | 1632 | 768 |
|
|
432
|
+
| ML-KEM-768 | 1184 | 2400 | 1088 |
|
|
433
|
+
| ML-KEM-1024 | 1568 | 3168 | 1568 |
|
|
434
|
+
| ML-DSA-44 | 1312 | 2560 | 2420 |
|
|
435
|
+
| ML-DSA-65 | 1952 | 4032 | 3309 |
|
|
436
|
+
| ML-DSA-87 | 2592 | 4896 | 4627 |
|
|
437
|
+
| Falcon512 | 897 | 1281 | 666 |
|
|
438
|
+
| Falcon1024 | 1793 | 2305 | 1280 |
|
|
439
|
+
| SLH-DSA-128f | 32 | 64 | 17088 |
|
|
440
|
+
| SLH-DSA-128s | 32 | 64 | 7856 |
|
|
441
|
+
| SLH-DSA-192f | 48 | 96 | 35664 |
|
|
442
|
+
| SLH-DSA-192s | 48 | 96 | 16224 |
|
|
443
|
+
| SLH-DSA-256f | 64 | 128 | 49856 |
|
|
444
|
+
| SLH-DSA-256s | 64 | 128 | 29792 |
|
|
445
|
+
|
|
359
446
|
|
|
360
447
|
## License
|
|
361
448
|
|
package/_crystals.d.ts
CHANGED
|
@@ -55,9 +55,15 @@ type Crystals<T extends TypedArray> = {
|
|
|
55
55
|
smod: (a: number, modulo?: number) => number;
|
|
56
56
|
nttZetas: T;
|
|
57
57
|
NTT: {
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Forward transform in place. Mutates and returns `r`.
|
|
60
|
+
* Kyber-mode input coefficients must already use canonical representatives in `[0, Q)`.
|
|
61
|
+
*/
|
|
59
62
|
encode: (r: T) => T;
|
|
60
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Inverse transform in place. Mutates and returns `r`.
|
|
65
|
+
* Kyber-mode input coefficients must already use canonical representatives in `[0, Q)`.
|
|
66
|
+
*/
|
|
61
67
|
decode: (r: T) => T;
|
|
62
68
|
};
|
|
63
69
|
bitsCoder: (d: number, c: Coder<number, number>) => BytesCoderLen<T>;
|
|
@@ -117,4 +123,3 @@ export declare const XOF128: TRet<XOF>;
|
|
|
117
123
|
*/
|
|
118
124
|
export declare const XOF256: TRet<XOF>;
|
|
119
125
|
export {};
|
|
120
|
-
//# sourceMappingURL=_crystals.d.ts.map
|
package/_crystals.js
CHANGED
|
@@ -31,7 +31,7 @@ export const genCrystals = (opts) => {
|
|
|
31
31
|
// Normalize JS `%` into the canonical Z_m representative `[0, modulo-1]` expected by
|
|
32
32
|
// FIPS 203 ยง2.3 / FIPS 204 ยง2.3 before downstream mod-q arithmetic.
|
|
33
33
|
const mod = (a, modulo = Q) => {
|
|
34
|
-
const result = a % modulo | 0;
|
|
34
|
+
const result = (a % modulo) | 0;
|
|
35
35
|
return (result >= 0 ? result | 0 : (modulo + result) | 0) | 0;
|
|
36
36
|
};
|
|
37
37
|
// FIPS 204 ยง7.4 uses the centered `mod ยฑ` representative for low bits, keeping the
|
|
@@ -57,14 +57,34 @@ export const genCrystals = (opts) => {
|
|
|
57
57
|
// Explained: https://electricdusk.com/ntt.html
|
|
58
58
|
// Kyber has slightly different params, since there is no 512th primitive root of unity mod q,
|
|
59
59
|
// only 256th primitive root of unity mod. Which also complicates MultiplyNTT.
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
sub: (a, b) => mod((a | 0) - (b | 0)) | 0,
|
|
63
|
-
mul: (a, b) => mod((a | 0) * (b | 0)) | 0,
|
|
64
|
-
inv: (_a) => {
|
|
65
|
-
throw new Error('not implemented');
|
|
66
|
-
},
|
|
60
|
+
const inv = (_a) => {
|
|
61
|
+
throw new Error('not implemented');
|
|
67
62
|
};
|
|
63
|
+
// ML-KEM (Kyber) polynomials always enter the transform reduced to [0, Q), so add/sub only
|
|
64
|
+
// need one conditional correction instead of `%`; measured ~20% faster NTT there.
|
|
65
|
+
// ML-DSA keeps the generic mod() path on purpose: its first forward stage sees centered
|
|
66
|
+
// (negative) coefficients, and `sub(a, t)` can drop below -Q (t is a mul output in [0, Q)),
|
|
67
|
+
// so a single correction is not enough. A guarded fast path with mod() fallback was measured
|
|
68
|
+
// slower than plain `%` for the 23-bit Q (V8 int32 modulo is one div; the branches lose).
|
|
69
|
+
const field = isKyber
|
|
70
|
+
? {
|
|
71
|
+
add: (a, b) => {
|
|
72
|
+
const r = (a + b) | 0;
|
|
73
|
+
return r >= Q ? (r - Q) | 0 : r;
|
|
74
|
+
},
|
|
75
|
+
sub: (a, b) => {
|
|
76
|
+
const r = (a - b) | 0;
|
|
77
|
+
return r < 0 ? (r + Q) | 0 : r;
|
|
78
|
+
},
|
|
79
|
+
mul: (a, b) => mod((a | 0) * (b | 0)) | 0,
|
|
80
|
+
inv,
|
|
81
|
+
}
|
|
82
|
+
: {
|
|
83
|
+
add: (a, b) => mod((a | 0) + (b | 0)) | 0,
|
|
84
|
+
sub: (a, b) => mod((a | 0) - (b | 0)) | 0,
|
|
85
|
+
mul: (a, b) => mod((a | 0) * (b | 0)) | 0,
|
|
86
|
+
inv,
|
|
87
|
+
};
|
|
68
88
|
const nttOpts = {
|
|
69
89
|
N,
|
|
70
90
|
roots: nttZetas,
|
|
@@ -91,6 +111,13 @@ export const genCrystals = (opts) => {
|
|
|
91
111
|
// Pack one little-endian `d`-bit word per coefficient, matching FIPS 203 ByteEncode /
|
|
92
112
|
// ByteDecode and the FIPS 204 BitsToBytes-based polynomial packing helpers.
|
|
93
113
|
const bitsCoder = (d, c) => {
|
|
114
|
+
// Validate the carry shape once: JS bitwise operations silently truncate wider accumulators.
|
|
115
|
+
for (let i = 0, bufLen = 0; i < N; i++) {
|
|
116
|
+
bufLen += d;
|
|
117
|
+
if (bufLen > 32)
|
|
118
|
+
getMask(bufLen);
|
|
119
|
+
bufLen %= 8;
|
|
120
|
+
}
|
|
94
121
|
const mask = getMask(d);
|
|
95
122
|
const bytesLen = d * (N / 8);
|
|
96
123
|
return {
|
|
@@ -101,8 +128,10 @@ export const genCrystals = (opts) => {
|
|
|
101
128
|
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
|
|
102
129
|
buf |= (c.encode(poly[i]) & mask) << bufLen;
|
|
103
130
|
bufLen += d;
|
|
131
|
+
// Take the low byte directly: `& 0xff` matches the previous getMask(bufLen) result
|
|
132
|
+
// after Uint8Array truncation, without a validated function call per output byte.
|
|
104
133
|
for (; bufLen >= 8; bufLen -= 8, buf >>= 8)
|
|
105
|
-
r[pos++] = buf &
|
|
134
|
+
r[pos++] = buf & 0xff;
|
|
106
135
|
}
|
|
107
136
|
return r;
|
|
108
137
|
},
|
|
@@ -198,4 +227,3 @@ export const XOF128 = /* @__PURE__ */ createXofShake(shake128);
|
|
|
198
227
|
* ```
|
|
199
228
|
*/
|
|
200
229
|
export const XOF256 = /* @__PURE__ */ createXofShake(shake256);
|
|
201
|
-
//# sourceMappingURL=_crystals.js.map
|
package/falcon.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type FalconAttached = CryptoKeys & {
|
|
|
22
22
|
* @param sig Attached Falcon signature bytes.
|
|
23
23
|
* @param publicKey Falcon public key bytes.
|
|
24
24
|
* @param opts Optional verification options.
|
|
25
|
-
* @returns
|
|
25
|
+
* @returns Fresh message bytes that do not alias either input when the signature is valid.
|
|
26
26
|
*/
|
|
27
27
|
open(sig: Uint8Array, publicKey: Uint8Array, opts?: VerOpts): Uint8Array;
|
|
28
28
|
};
|
|
@@ -81,4 +81,3 @@ export declare const falcon1024: TRet<Falcon>;
|
|
|
81
81
|
export declare const falcon1024padded: TRet<Falcon>;
|
|
82
82
|
export declare const __tests: any;
|
|
83
83
|
export {};
|
|
84
|
-
//# sourceMappingURL=falcon.d.ts.map
|