Skip to content

Commit 1e8b312

Browse files
authored
feat: add SLH-DSA sign and verify support (#1030)
1 parent 87954b8 commit 1e8b312

23 files changed

Lines changed: 1789 additions & 354 deletions

.docs/implementation-coverage.md

Lines changed: 281 additions & 160 deletions
Large diffs are not rendered by default.

docs/content/docs/api/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RNQC mirrors the Node.js `crypto` API while adding specialized high-performance
2222
<Card title="HMAC" href="/docs/api/hmac">
2323
Keyed-hash message authentication.
2424
</Card>
25-
<Card title="Random" href="/docs/api/random">
25+
<Card title="Random" href="/docs/api/random">
2626
CSPRNG, UUIDs, and random integers.
2727
</Card>
2828
<Card title="Keys" href="/docs/api/keys">
@@ -77,7 +77,7 @@ RNQC mirrors the Node.js `crypto` API while adding specialized high-performance
7777

7878
<Cards>
7979
<Card title="Post-Quantum (PQC)" href="/docs/api/pqc">
80-
ML-DSA signatures and ML-KEM key encapsulation.
80+
ML-DSA / SLH-DSA signatures and ML-KEM key encapsulation.
8181
</Card>
8282
<Card title="KMAC" href="/docs/api/kmac">
8383
Keccak Message Authentication Code (KMAC128/KMAC256).

docs/content/docs/api/pqc.mdx

Lines changed: 117 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Post-Quantum Cryptography
3-
description: Quantum-resistant algorithms (ML-DSA, ML-KEM)
3+
description: Quantum-resistant algorithms (ML-DSA, ML-KEM, SLH-DSA)
44
---
55

66
import { Callout } from 'fumadocs-ui/components/callout';
@@ -9,14 +9,17 @@ import { TypeTable } from 'fumadocs-ui/components/type-table';
99
**Post-Quantum Cryptography (PQC)** provides cryptographic algorithms that are secure against both classical and quantum computers. RNQC implements the NIST standardized lattice-based algorithms via OpenSSL 3.6+.
1010

1111
<Callout type="info" title="Why Post-Quantum?">
12-
Quantum computers threaten RSA, ECDSA, and ECDH. The PQC algorithms below are NIST-standardized replacements designed to resist quantum attacks while running efficiently on classical hardware.
12+
Quantum computers threaten RSA, ECDSA, and ECDH. The PQC algorithms below are
13+
NIST-standardized replacements designed to resist quantum attacks while
14+
running efficiently on classical hardware.
1315
</Callout>
1416

1517
## Table of Contents
1618

1719
- [Algorithms](#algorithms)
1820
- [ML-DSA (Digital Signatures)](#ml-dsa-digital-signatures)
1921
- [ML-KEM (Key Encapsulation)](#ml-kem-key-encapsulation)
22+
- [SLH-DSA (Hash-Based Digital Signatures)](#slh-dsa-hash-based-digital-signatures)
2023
- [WebCrypto API](#webcrypto-api)
2124
- [Real-World Examples](#real-world-examples)
2225

@@ -26,21 +29,41 @@ import { TypeTable } from 'fumadocs-ui/components/type-table';
2629

2730
Module Lattice Digital Signature Algorithm. Replacement for RSA and ECDSA signatures.
2831

29-
| Parameter Set | Security Level | Public Key | Signature | Use Case |
30-
|:-------------|:--------------|:-----------|:----------|:---------|
31-
| `ML-DSA-44` | NIST Level 2 | 1,312 B | 2,420 B | General purpose |
32-
| `ML-DSA-65` | NIST Level 3 | 1,952 B | 3,309 B | Recommended |
33-
| `ML-DSA-87` | NIST Level 5 | 2,592 B | 4,627 B | Maximum security |
32+
| Parameter Set | Security Level | Public Key | Signature | Use Case |
33+
| :------------ | :------------- | :--------- | :-------- | :--------------- |
34+
| `ML-DSA-44` | NIST Level 2 | 1,312 B | 2,420 B | General purpose |
35+
| `ML-DSA-65` | NIST Level 3 | 1,952 B | 3,309 B | Recommended |
36+
| `ML-DSA-87` | NIST Level 5 | 2,592 B | 4,627 B | Maximum security |
3437

3538
### ML-KEM (FIPS 203)
3639

3740
Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange.
3841

3942
| Parameter Set | Security Level | Public Key | Ciphertext | Shared Secret |
40-
|:-------------|:--------------|:-----------|:-----------|:-------------|
41-
| `ML-KEM-512` | NIST Level 1 | 800 B | 768 B | 32 B |
42-
| `ML-KEM-768` | NIST Level 3 | 1,184 B | 1,088 B | 32 B |
43-
| `ML-KEM-1024` | NIST Level 5 | 1,568 B | 1,568 B | 32 B |
43+
| :------------ | :------------- | :--------- | :--------- | :------------ |
44+
| `ML-KEM-512` | NIST Level 1 | 800 B | 768 B | 32 B |
45+
| `ML-KEM-768` | NIST Level 3 | 1,184 B | 1,088 B | 32 B |
46+
| `ML-KEM-1024` | NIST Level 5 | 1,568 B | 1,568 B | 32 B |
47+
48+
### SLH-DSA (FIPS 205)
49+
50+
Stateless Hash-Based Digital Signature Algorithm (formerly SPHINCS+). A hash-based alternative to ML-DSA — security relies only on the underlying hash function, making it a conservative choice when lattice assumptions are a concern. Each parameter set comes in `s` (small signature, slow) and `f` (fast, larger signature) variants.
51+
52+
| Parameter Set | Security Level | Public Key | Signature | Notes |
53+
| :----------------------------------------- | :------------- | :--------- | :-------- | :-------- |
54+
| `SLH-DSA-SHA2-128s` / `SLH-DSA-SHAKE-128s` | NIST Level 1 | 32 B | 7,856 B | Small sig |
55+
| `SLH-DSA-SHA2-128f` / `SLH-DSA-SHAKE-128f` | NIST Level 1 | 32 B | 17,088 B | Fast sign |
56+
| `SLH-DSA-SHA2-192s` / `SLH-DSA-SHAKE-192s` | NIST Level 3 | 48 B | 16,224 B | Small sig |
57+
| `SLH-DSA-SHA2-192f` / `SLH-DSA-SHAKE-192f` | NIST Level 3 | 48 B | 35,664 B | Fast sign |
58+
| `SLH-DSA-SHA2-256s` / `SLH-DSA-SHAKE-256s` | NIST Level 5 | 64 B | 29,792 B | Small sig |
59+
| `SLH-DSA-SHA2-256f` / `SLH-DSA-SHAKE-256f` | NIST Level 5 | 64 B | 49,856 B | Fast sign |
60+
61+
<Callout type="warn" title="Performance Tradeoff">
62+
The `s` variants produce smaller signatures but signing is markedly slower
63+
than ML-DSA. The `f` variants sign faster but emit signatures 4–6× larger. For
64+
most applications ML-DSA is preferable; reach for SLH-DSA when a hash-only
65+
security assumption is required.
66+
</Callout>
4467

4568
---
4669

@@ -51,11 +74,7 @@ Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange.
5174
Generate ML-DSA key pairs and sign/verify using the standard `crypto` API:
5275

5376
```ts
54-
import {
55-
generateKeyPairSync,
56-
sign,
57-
verify
58-
} from 'react-native-quick-crypto';
77+
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
5978

6079
// Generate ML-DSA-65 key pair
6180
const { publicKey, privateKey } = generateKeyPairSync('ml-dsa-65');
@@ -87,7 +106,7 @@ import { createPublicKey, createPrivateKey } from 'react-native-quick-crypto';
87106
const imported = createPublicKey({
88107
key: pubDer,
89108
format: 'der',
90-
type: 'spki'
109+
type: 'spki',
91110
});
92111
```
93112

@@ -103,7 +122,7 @@ ML-KEM uses **encapsulation** rather than key exchange. One party encapsulates a
103122
import {
104123
generateKeyPairSync,
105124
encapsulate,
106-
decapsulate
125+
decapsulate,
107126
} from 'react-native-quick-crypto';
108127

109128
// Generate ML-KEM-768 key pair
@@ -120,6 +139,29 @@ console.log(sharedSecret.equals(recovered)); // true
120139

121140
---
122141

142+
## SLH-DSA (Hash-Based Digital Signatures)
143+
144+
### Node.js API
145+
146+
Generate SLH-DSA key pairs and sign/verify using the standard `crypto` API. Twelve parameter sets are available: `slh-dsa-{sha2,shake}-{128,192,256}{s,f}`.
147+
148+
```ts
149+
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
150+
151+
// Generate SLH-DSA-SHA2-128f key pair (fast variant)
152+
const { publicKey, privateKey } = generateKeyPairSync('slh-dsa-sha2-128f');
153+
154+
// Sign
155+
const message = Buffer.from('hash-based, quantum-safe message');
156+
const signature = sign(null, message, privateKey);
157+
158+
// Verify
159+
const isValid = verify(null, message, publicKey, signature);
160+
console.log('Valid:', isValid); // true
161+
```
162+
163+
---
164+
123165
## WebCrypto API
124166

125167
PQC algorithms are fully supported through the SubtleCrypto interface.
@@ -130,26 +172,25 @@ PQC algorithms are fully supported through the SubtleCrypto interface.
130172
import { subtle } from 'react-native-quick-crypto';
131173

132174
// Generate key pair
133-
const keyPair = await subtle.generateKey(
134-
{ name: 'ML-DSA-65' },
135-
true,
136-
['sign', 'verify']
137-
);
175+
const keyPair = await subtle.generateKey({ name: 'ML-DSA-65' }, true, [
176+
'sign',
177+
'verify',
178+
]);
138179

139180
// Sign
140181
const data = new TextEncoder().encode('quantum-safe data');
141182
const signature = await subtle.sign(
142183
{ name: 'ML-DSA-65' },
143184
keyPair.privateKey,
144-
data
185+
data,
145186
);
146187

147188
// Verify
148189
const isValid = await subtle.verify(
149190
{ name: 'ML-DSA-65' },
150191
keyPair.publicKey,
151192
signature,
152-
data
193+
data,
153194
);
154195
```
155196

@@ -159,23 +200,22 @@ const isValid = await subtle.verify(
159200
import { subtle } from 'react-native-quick-crypto';
160201

161202
// Generate encapsulation key pair
162-
const keyPair = await subtle.generateKey(
163-
{ name: 'ML-KEM-768' },
164-
true,
165-
['deriveBits', 'deriveKey']
166-
);
203+
const keyPair = await subtle.generateKey({ name: 'ML-KEM-768' }, true, [
204+
'deriveBits',
205+
'deriveKey',
206+
]);
167207

168208
// Encapsulate: get shared secret bits + ciphertext
169209
const { sharedSecret, ciphertext } = await subtle.encapsulateBits(
170210
{ name: 'ML-KEM-768' },
171-
keyPair.publicKey
211+
keyPair.publicKey,
172212
);
173213

174214
// Decapsulate: recover shared secret
175215
const recovered = await subtle.decapsulateBits(
176216
{ name: 'ML-KEM-768' },
177217
keyPair.privateKey,
178-
ciphertext
218+
ciphertext,
179219
);
180220

181221
// Or derive a key directly from encapsulation
@@ -184,7 +224,7 @@ const { key: aesKey, ciphertext: ct } = await subtle.encapsulateKey(
184224
keyPair.publicKey,
185225
{ name: 'AES-GCM', length: 256 },
186226
true,
187-
['encrypt', 'decrypt']
227+
['encrypt', 'decrypt'],
188228
);
189229

190230
// Decapsulate to get the same AES key
@@ -194,16 +234,43 @@ const recoveredKey = await subtle.decapsulateKey(
194234
ct,
195235
{ name: 'AES-GCM', length: 256 },
196236
true,
197-
['encrypt', 'decrypt']
237+
['encrypt', 'decrypt'],
238+
);
239+
```
240+
241+
### SLH-DSA via SubtleCrypto
242+
243+
```ts
244+
import { subtle } from 'react-native-quick-crypto';
245+
246+
// Generate key pair (use the canonical FIPS 205 name)
247+
const keyPair = await subtle.generateKey({ name: 'SLH-DSA-SHA2-128f' }, true, [
248+
'sign',
249+
'verify',
250+
]);
251+
252+
const data = new TextEncoder().encode('signed with hash-based PQC');
253+
const signature = await subtle.sign(
254+
{ name: 'SLH-DSA-SHA2-128f' },
255+
keyPair.privateKey,
256+
data,
257+
);
258+
259+
const isValid = await subtle.verify(
260+
{ name: 'SLH-DSA-SHA2-128f' },
261+
keyPair.publicKey,
262+
signature,
263+
data,
198264
);
199265
```
200266

201267
### Key Export Formats
202268

203-
| Algorithm | `spki` | `pkcs8` | `jwk` | `raw-public` | `raw-seed` |
204-
|:----------|:------:|:-------:|:-----:|:------------:|:----------:|
205-
| ML-DSA-44/65/87 ||||||
206-
| ML-KEM-512/768/1024 ||||||
269+
| Algorithm | `spki` | `pkcs8` | `jwk` | `raw-public` | `raw-seed` |
270+
| :---------------------------------------- | :----: | :-----: | :---: | :----------: | :--------: |
271+
| ML-DSA-44/65/87 ||||||
272+
| ML-KEM-512/768/1024 ||||||
273+
| `SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` ||||||
207274

208275
```ts
209276
// Export ML-DSA public key as JWK
@@ -221,7 +288,7 @@ const imported = await subtle.importKey(
221288
rawPub,
222289
{ name: 'ML-DSA-65' },
223290
true,
224-
['verify']
291+
['verify'],
225292
);
226293
```
227294

@@ -234,11 +301,7 @@ const imported = await subtle.importKey(
234301
Combine Ed25519 with ML-DSA for defense-in-depth during the quantum transition:
235302

236303
```ts
237-
import {
238-
generateKeyPairSync,
239-
sign,
240-
verify
241-
} from 'react-native-quick-crypto';
304+
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
242305

243306
function hybridSign(message: Buffer) {
244307
const ed = generateKeyPairSync('ed25519');
@@ -250,18 +313,22 @@ function hybridSign(message: Buffer) {
250313
return {
251314
message,
252315
signatures: { ed25519: edSig, mlDsa65: pqcSig },
253-
publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey }
316+
publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey },
254317
};
255318
}
256319

257320
function hybridVerify(signed: ReturnType<typeof hybridSign>): boolean {
258321
const edValid = verify(
259-
null, signed.message,
260-
signed.publicKeys.ed25519, signed.signatures.ed25519
322+
null,
323+
signed.message,
324+
signed.publicKeys.ed25519,
325+
signed.signatures.ed25519,
261326
);
262327
const pqcValid = verify(
263-
null, signed.message,
264-
signed.publicKeys.mlDsa65, signed.signatures.mlDsa65
328+
null,
329+
signed.message,
330+
signed.publicKeys.mlDsa65,
331+
signed.signatures.mlDsa65,
265332
);
266333

267334
// Both must pass
@@ -281,7 +348,7 @@ import {
281348
createCipheriv,
282349
createDecipheriv,
283350
createHash,
284-
randomBytes
351+
randomBytes,
285352
} from 'react-native-quick-crypto';
286353

287354
// Server publishes its ML-KEM public key

0 commit comments

Comments
 (0)