diff --git a/.docs/implementation-coverage.md b/.docs/implementation-coverage.md
index e05742a4..b7f67f85 100644
--- a/.docs/implementation-coverage.md
+++ b/.docs/implementation-coverage.md
@@ -14,6 +14,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
- **ML-DSA** (Module Lattice Digital Signature Algorithm, FIPS 204) - ML-DSA-44, ML-DSA-65, ML-DSA-87
- **ML-KEM** (Module Lattice Key Encapsulation Mechanism, FIPS 203) - ML-KEM-512, ML-KEM-768, ML-KEM-1024
+- **SLH-DSA** (Stateless Hash-Based Digital Signature, FIPS 205) - SLH-DSA-SHA2-{128,192,256}{s,f}, SLH-DSA-SHAKE-{128,192,256}{s,f}
These algorithms provide quantum-resistant cryptography.
@@ -184,31 +185,55 @@ These algorithms provide quantum-resistant cryptography.
## `crypto.generateKeyPair`
-| type | Status |
-| --------- | :----: |
-| `rsa` | ✅ |
-| `rsa-pss` | ✅ |
-| `dsa` | ✅ |
-| `ec` | ✅ |
-| `ed25519` | ✅ |
-| `ed448` | ✅ |
-| `x25519` | ✅ |
-| `x448` | ✅ |
-| `dh` | ✅ |
+| type | Status |
+| -------------------- | :----: |
+| `rsa` | ✅ |
+| `rsa-pss` | ✅ |
+| `dsa` | ✅ |
+| `ec` | ✅ |
+| `ed25519` | ✅ |
+| `ed448` | ✅ |
+| `x25519` | ✅ |
+| `x448` | ✅ |
+| `dh` | ✅ |
+| `slh-dsa-sha2-128s` | ✅ |
+| `slh-dsa-sha2-128f` | ✅ |
+| `slh-dsa-sha2-192s` | ✅ |
+| `slh-dsa-sha2-192f` | ✅ |
+| `slh-dsa-sha2-256s` | ✅ |
+| `slh-dsa-sha2-256f` | ✅ |
+| `slh-dsa-shake-128s` | ✅ |
+| `slh-dsa-shake-128f` | ✅ |
+| `slh-dsa-shake-192s` | ✅ |
+| `slh-dsa-shake-192f` | ✅ |
+| `slh-dsa-shake-256s` | ✅ |
+| `slh-dsa-shake-256f` | ✅ |
## `crypto.generateKeyPairSync`
-| type | Status |
-| --------- | :----: |
-| `rsa` | ✅ |
-| `rsa-pss` | ✅ |
-| `dsa` | ✅ |
-| `ec` | ✅ |
-| `ed25519` | ✅ |
-| `ed448` | ✅ |
-| `x25519` | ✅ |
-| `x448` | ✅ |
-| `dh` | ✅ |
+| type | Status |
+| -------------------- | :----: |
+| `rsa` | ✅ |
+| `rsa-pss` | ✅ |
+| `dsa` | ✅ |
+| `ec` | ✅ |
+| `ed25519` | ✅ |
+| `ed448` | ✅ |
+| `x25519` | ✅ |
+| `x448` | ✅ |
+| `dh` | ✅ |
+| `slh-dsa-sha2-128s` | ✅ |
+| `slh-dsa-sha2-128f` | ✅ |
+| `slh-dsa-sha2-192s` | ✅ |
+| `slh-dsa-sha2-192f` | ✅ |
+| `slh-dsa-sha2-256s` | ✅ |
+| `slh-dsa-sha2-256f` | ✅ |
+| `slh-dsa-shake-128s` | ✅ |
+| `slh-dsa-shake-128f` | ✅ |
+| `slh-dsa-shake-192s` | ✅ |
+| `slh-dsa-shake-192f` | ✅ |
+| `slh-dsa-shake-256s` | ✅ |
+| `slh-dsa-shake-256f` | ✅ |
## `crypto.generateKeySync`
@@ -219,25 +244,49 @@ These algorithms provide quantum-resistant cryptography.
## `crypto.sign`
-| Algorithm | Status |
-| ------------------- | :----: |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `RSA-PSS` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `RSA-PSS` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
## `crypto.verify`
-| Algorithm | Status |
-| ------------------- | :----: |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `RSA-PSS` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `RSA-PSS` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
## Extended Ciphers (Beyond Node.js API)
@@ -356,30 +405,42 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
## `subtle.exportKey`
-| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
-| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: |
-| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
-| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
-| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
-| `AES-KW` | | | ✅ | ✅ | ✅ | | |
-| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
-| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
-| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `HMAC` | | | ✅ | ✅ | ✅ | | |
-| `KMAC128` | | | ✅ | ✅ | ✅ | | |
-| `KMAC256` | | | ✅ | ✅ | ✅ | | |
-| `ML-DSA-44` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-DSA-65` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-DSA-87` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-512` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-768` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-1024` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | |
-| `RSA-PSS` | ✅ | ✅ | ✅ | | | | |
-| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | |
+| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
+| -------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: |
+| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
+| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
+| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
+| `AES-KW` | | | ✅ | ✅ | ✅ | | |
+| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
+| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
+| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `HMAC` | | | ✅ | ✅ | ✅ | | |
+| `KMAC128` | | | ✅ | ✅ | ✅ | | |
+| `KMAC256` | | | ✅ | ✅ | ✅ | | |
+| `ML-DSA-44` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-DSA-65` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-DSA-87` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-512` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-768` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-1024` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | |
+| `RSA-PSS` | ✅ | ✅ | ✅ | | | | |
+| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | |
+| `SLH-DSA-SHA2-128s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
- ` ` - not implemented in Node
- ❌ - implemented in Node, not RNQC
@@ -389,23 +450,35 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
### `CryptoKeyPair` algorithms
-| Algorithm | Status |
-| ------------------- | :----: |
-| `ECDH` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `ML-KEM-512` | ✅ |
-| `ML-KEM-768` | ✅ |
-| `ML-KEM-1024` | ✅ |
-| `RSA-OAEP` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `X25519` | ✅ |
-| `X448` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `ECDH` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `ML-KEM-512` | ✅ |
+| `ML-KEM-768` | ✅ |
+| `ML-KEM-1024` | ✅ |
+| `RSA-OAEP` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
+| `X25519` | ✅ |
+| `X448` | ✅ |
### `CryptoKey` algorithms
@@ -423,53 +496,77 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
## `subtle.importKey`
-| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
-| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: |
-| `Argon2d` | | | | | ✅ | | |
-| `Argon2i` | | | | | ✅ | | |
-| `Argon2id` | | | | | ✅ | | |
-| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
-| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
-| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
-| `AES-KW` | | | ✅ | ✅ | ✅ | | |
-| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
-| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
-| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `HKDF` | | | | ✅ | ✅ | | |
-| `HMAC` | | | ✅ | ✅ | ✅ | | |
-| `KMAC128` | | | ✅ | ✅ | ✅ | | |
-| `KMAC256` | | | ✅ | ✅ | ✅ | | |
-| `ML-DSA-44` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-DSA-65` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-DSA-87` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-512` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-768` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `ML-KEM-1024` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
-| `PBKDF2` | | | | ✅ | ✅ | | |
-| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | |
-| `RSA-PSS` | ✅ | ✅ | ✅ | | | | |
-| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | |
-| `X25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `X448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
+| -------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: |
+| `Argon2d` | | | | | ✅ | | |
+| `Argon2i` | | | | | ✅ | | |
+| `Argon2id` | | | | | ✅ | | |
+| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
+| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
+| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
+| `AES-KW` | | | ✅ | ✅ | ✅ | | |
+| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
+| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
+| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `HKDF` | | | | ✅ | ✅ | | |
+| `HMAC` | | | ✅ | ✅ | ✅ | | |
+| `KMAC128` | | | ✅ | ✅ | ✅ | | |
+| `KMAC256` | | | ✅ | ✅ | ✅ | | |
+| `ML-DSA-44` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-DSA-65` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-DSA-87` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-512` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-768` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `ML-KEM-1024` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `PBKDF2` | | | | ✅ | ✅ | | |
+| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | |
+| `RSA-PSS` | ✅ | ✅ | ✅ | | | | |
+| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | |
+| `SLH-DSA-SHA2-128s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ | ✅ | ❌ | | | ✅ | ✅ |
+| `X25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `X448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
## `subtle.sign`
-| Algorithm | Status |
-| ------------------- | :----: |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
-| `KMAC128` | ✅ |
-| `KMAC256` | ✅ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `KMAC128` | ✅ |
+| `KMAC256` | ✅ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
## `subtle.unwrapKey`
@@ -487,46 +584,70 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
### unwrapped key algorithms
-| Algorithm | Status |
-| ------------------- | :----: |
-| `AES-CBC` | ✅ |
-| `AES-CTR` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-KW` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
-| `ECDH` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `ML-KEM-512` | ✅ |
-| `ML-KEM-768` | ✅ |
-| `ML-KEM-1024` | ✅ |
-| `RSA-OAEP` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `X25519` | ✅ |
-| `X448` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `AES-CBC` | ✅ |
+| `AES-CTR` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-KW` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
+| `ECDH` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `ML-KEM-512` | ✅ |
+| `ML-KEM-768` | ✅ |
+| `ML-KEM-1024` | ✅ |
+| `RSA-OAEP` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
+| `X25519` | ✅ |
+| `X448` | ✅ |
## `subtle.verify`
-| Algorithm | Status |
-| ------------------- | :----: |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
-| `KMAC128` | ✅ |
-| `KMAC256` | ✅ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
+| Algorithm | Status |
+| -------------------- | :----: |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `KMAC128` | ✅ |
+| `KMAC256` | ✅ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `SLH-DSA-SHA2-128s` | ✅ |
+| `SLH-DSA-SHA2-128f` | ✅ |
+| `SLH-DSA-SHA2-192s` | ✅ |
+| `SLH-DSA-SHA2-192f` | ✅ |
+| `SLH-DSA-SHA2-256s` | ✅ |
+| `SLH-DSA-SHA2-256f` | ✅ |
+| `SLH-DSA-SHAKE-128s` | ✅ |
+| `SLH-DSA-SHAKE-128f` | ✅ |
+| `SLH-DSA-SHAKE-192s` | ✅ |
+| `SLH-DSA-SHAKE-192f` | ✅ |
+| `SLH-DSA-SHAKE-256s` | ✅ |
+| `SLH-DSA-SHAKE-256f` | ✅ |
## `subtle.wrapKey`
diff --git a/docs/content/docs/api/index.mdx b/docs/content/docs/api/index.mdx
index 9ddfff82..b902698b 100644
--- a/docs/content/docs/api/index.mdx
+++ b/docs/content/docs/api/index.mdx
@@ -22,7 +22,7 @@ RNQC mirrors the Node.js `crypto` API while adding specialized high-performance
Keyed-hash message authentication.
-
+
CSPRNG, UUIDs, and random integers.
@@ -77,7 +77,7 @@ RNQC mirrors the Node.js `crypto` API while adding specialized high-performance
- ML-DSA signatures and ML-KEM key encapsulation.
+ ML-DSA / SLH-DSA signatures and ML-KEM key encapsulation.
Keccak Message Authentication Code (KMAC128/KMAC256).
diff --git a/docs/content/docs/api/pqc.mdx b/docs/content/docs/api/pqc.mdx
index 38e30e0a..add455f8 100644
--- a/docs/content/docs/api/pqc.mdx
+++ b/docs/content/docs/api/pqc.mdx
@@ -1,6 +1,6 @@
---
title: Post-Quantum Cryptography
-description: Quantum-resistant algorithms (ML-DSA, ML-KEM)
+description: Quantum-resistant algorithms (ML-DSA, ML-KEM, SLH-DSA)
---
import { Callout } from 'fumadocs-ui/components/callout';
@@ -9,7 +9,9 @@ import { TypeTable } from 'fumadocs-ui/components/type-table';
**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+.
- 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.
+ 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.
## Table of Contents
@@ -17,6 +19,7 @@ import { TypeTable } from 'fumadocs-ui/components/type-table';
- [Algorithms](#algorithms)
- [ML-DSA (Digital Signatures)](#ml-dsa-digital-signatures)
- [ML-KEM (Key Encapsulation)](#ml-kem-key-encapsulation)
+- [SLH-DSA (Hash-Based Digital Signatures)](#slh-dsa-hash-based-digital-signatures)
- [WebCrypto API](#webcrypto-api)
- [Real-World Examples](#real-world-examples)
@@ -26,21 +29,41 @@ import { TypeTable } from 'fumadocs-ui/components/type-table';
Module Lattice Digital Signature Algorithm. Replacement for RSA and ECDSA signatures.
-| Parameter Set | Security Level | Public Key | Signature | Use Case |
-|:-------------|:--------------|:-----------|:----------|:---------|
-| `ML-DSA-44` | NIST Level 2 | 1,312 B | 2,420 B | General purpose |
-| `ML-DSA-65` | NIST Level 3 | 1,952 B | 3,309 B | Recommended |
-| `ML-DSA-87` | NIST Level 5 | 2,592 B | 4,627 B | Maximum security |
+| Parameter Set | Security Level | Public Key | Signature | Use Case |
+| :------------ | :------------- | :--------- | :-------- | :--------------- |
+| `ML-DSA-44` | NIST Level 2 | 1,312 B | 2,420 B | General purpose |
+| `ML-DSA-65` | NIST Level 3 | 1,952 B | 3,309 B | Recommended |
+| `ML-DSA-87` | NIST Level 5 | 2,592 B | 4,627 B | Maximum security |
### ML-KEM (FIPS 203)
Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange.
| Parameter Set | Security Level | Public Key | Ciphertext | Shared Secret |
-|:-------------|:--------------|:-----------|:-----------|:-------------|
-| `ML-KEM-512` | NIST Level 1 | 800 B | 768 B | 32 B |
-| `ML-KEM-768` | NIST Level 3 | 1,184 B | 1,088 B | 32 B |
-| `ML-KEM-1024` | NIST Level 5 | 1,568 B | 1,568 B | 32 B |
+| :------------ | :------------- | :--------- | :--------- | :------------ |
+| `ML-KEM-512` | NIST Level 1 | 800 B | 768 B | 32 B |
+| `ML-KEM-768` | NIST Level 3 | 1,184 B | 1,088 B | 32 B |
+| `ML-KEM-1024` | NIST Level 5 | 1,568 B | 1,568 B | 32 B |
+
+### SLH-DSA (FIPS 205)
+
+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.
+
+| Parameter Set | Security Level | Public Key | Signature | Notes |
+| :----------------------------------------- | :------------- | :--------- | :-------- | :-------- |
+| `SLH-DSA-SHA2-128s` / `SLH-DSA-SHAKE-128s` | NIST Level 1 | 32 B | 7,856 B | Small sig |
+| `SLH-DSA-SHA2-128f` / `SLH-DSA-SHAKE-128f` | NIST Level 1 | 32 B | 17,088 B | Fast sign |
+| `SLH-DSA-SHA2-192s` / `SLH-DSA-SHAKE-192s` | NIST Level 3 | 48 B | 16,224 B | Small sig |
+| `SLH-DSA-SHA2-192f` / `SLH-DSA-SHAKE-192f` | NIST Level 3 | 48 B | 35,664 B | Fast sign |
+| `SLH-DSA-SHA2-256s` / `SLH-DSA-SHAKE-256s` | NIST Level 5 | 64 B | 29,792 B | Small sig |
+| `SLH-DSA-SHA2-256f` / `SLH-DSA-SHAKE-256f` | NIST Level 5 | 64 B | 49,856 B | Fast sign |
+
+
+ The `s` variants produce smaller signatures but signing is markedly slower
+ than ML-DSA. The `f` variants sign faster but emit signatures 4–6× larger. For
+ most applications ML-DSA is preferable; reach for SLH-DSA when a hash-only
+ security assumption is required.
+
---
@@ -51,11 +74,7 @@ Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange.
Generate ML-DSA key pairs and sign/verify using the standard `crypto` API:
```ts
-import {
- generateKeyPairSync,
- sign,
- verify
-} from 'react-native-quick-crypto';
+import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
// Generate ML-DSA-65 key pair
const { publicKey, privateKey } = generateKeyPairSync('ml-dsa-65');
@@ -87,7 +106,7 @@ import { createPublicKey, createPrivateKey } from 'react-native-quick-crypto';
const imported = createPublicKey({
key: pubDer,
format: 'der',
- type: 'spki'
+ type: 'spki',
});
```
@@ -103,7 +122,7 @@ ML-KEM uses **encapsulation** rather than key exchange. One party encapsulates a
import {
generateKeyPairSync,
encapsulate,
- decapsulate
+ decapsulate,
} from 'react-native-quick-crypto';
// Generate ML-KEM-768 key pair
@@ -120,6 +139,29 @@ console.log(sharedSecret.equals(recovered)); // true
---
+## SLH-DSA (Hash-Based Digital Signatures)
+
+### Node.js API
+
+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}`.
+
+```ts
+import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
+
+// Generate SLH-DSA-SHA2-128f key pair (fast variant)
+const { publicKey, privateKey } = generateKeyPairSync('slh-dsa-sha2-128f');
+
+// Sign
+const message = Buffer.from('hash-based, quantum-safe message');
+const signature = sign(null, message, privateKey);
+
+// Verify
+const isValid = verify(null, message, publicKey, signature);
+console.log('Valid:', isValid); // true
+```
+
+---
+
## WebCrypto API
PQC algorithms are fully supported through the SubtleCrypto interface.
@@ -130,18 +172,17 @@ PQC algorithms are fully supported through the SubtleCrypto interface.
import { subtle } from 'react-native-quick-crypto';
// Generate key pair
-const keyPair = await subtle.generateKey(
- { name: 'ML-DSA-65' },
- true,
- ['sign', 'verify']
-);
+const keyPair = await subtle.generateKey({ name: 'ML-DSA-65' }, true, [
+ 'sign',
+ 'verify',
+]);
// Sign
const data = new TextEncoder().encode('quantum-safe data');
const signature = await subtle.sign(
{ name: 'ML-DSA-65' },
keyPair.privateKey,
- data
+ data,
);
// Verify
@@ -149,7 +190,7 @@ const isValid = await subtle.verify(
{ name: 'ML-DSA-65' },
keyPair.publicKey,
signature,
- data
+ data,
);
```
@@ -159,23 +200,22 @@ const isValid = await subtle.verify(
import { subtle } from 'react-native-quick-crypto';
// Generate encapsulation key pair
-const keyPair = await subtle.generateKey(
- { name: 'ML-KEM-768' },
- true,
- ['deriveBits', 'deriveKey']
-);
+const keyPair = await subtle.generateKey({ name: 'ML-KEM-768' }, true, [
+ 'deriveBits',
+ 'deriveKey',
+]);
// Encapsulate: get shared secret bits + ciphertext
const { sharedSecret, ciphertext } = await subtle.encapsulateBits(
{ name: 'ML-KEM-768' },
- keyPair.publicKey
+ keyPair.publicKey,
);
// Decapsulate: recover shared secret
const recovered = await subtle.decapsulateBits(
{ name: 'ML-KEM-768' },
keyPair.privateKey,
- ciphertext
+ ciphertext,
);
// Or derive a key directly from encapsulation
@@ -184,7 +224,7 @@ const { key: aesKey, ciphertext: ct } = await subtle.encapsulateKey(
keyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
);
// Decapsulate to get the same AES key
@@ -194,16 +234,43 @@ const recoveredKey = await subtle.decapsulateKey(
ct,
{ name: 'AES-GCM', length: 256 },
true,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
+);
+```
+
+### SLH-DSA via SubtleCrypto
+
+```ts
+import { subtle } from 'react-native-quick-crypto';
+
+// Generate key pair (use the canonical FIPS 205 name)
+const keyPair = await subtle.generateKey({ name: 'SLH-DSA-SHA2-128f' }, true, [
+ 'sign',
+ 'verify',
+]);
+
+const data = new TextEncoder().encode('signed with hash-based PQC');
+const signature = await subtle.sign(
+ { name: 'SLH-DSA-SHA2-128f' },
+ keyPair.privateKey,
+ data,
+);
+
+const isValid = await subtle.verify(
+ { name: 'SLH-DSA-SHA2-128f' },
+ keyPair.publicKey,
+ signature,
+ data,
);
```
### Key Export Formats
-| Algorithm | `spki` | `pkcs8` | `jwk` | `raw-public` | `raw-seed` |
-|:----------|:------:|:-------:|:-----:|:------------:|:----------:|
-| ML-DSA-44/65/87 | ✅ | ✅ | ✅ | ✅ | ✅ |
-| ML-KEM-512/768/1024 | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Algorithm | `spki` | `pkcs8` | `jwk` | `raw-public` | `raw-seed` |
+| :---------------------------------------- | :----: | :-----: | :---: | :----------: | :--------: |
+| ML-DSA-44/65/87 | ✅ | ✅ | ✅ | ✅ | ✅ |
+| ML-KEM-512/768/1024 | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` | ✅ | ✅ | ✅ | ✅ | ✅ |
```ts
// Export ML-DSA public key as JWK
@@ -221,7 +288,7 @@ const imported = await subtle.importKey(
rawPub,
{ name: 'ML-DSA-65' },
true,
- ['verify']
+ ['verify'],
);
```
@@ -234,11 +301,7 @@ const imported = await subtle.importKey(
Combine Ed25519 with ML-DSA for defense-in-depth during the quantum transition:
```ts
-import {
- generateKeyPairSync,
- sign,
- verify
-} from 'react-native-quick-crypto';
+import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
function hybridSign(message: Buffer) {
const ed = generateKeyPairSync('ed25519');
@@ -250,18 +313,22 @@ function hybridSign(message: Buffer) {
return {
message,
signatures: { ed25519: edSig, mlDsa65: pqcSig },
- publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey }
+ publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey },
};
}
function hybridVerify(signed: ReturnType): boolean {
const edValid = verify(
- null, signed.message,
- signed.publicKeys.ed25519, signed.signatures.ed25519
+ null,
+ signed.message,
+ signed.publicKeys.ed25519,
+ signed.signatures.ed25519,
);
const pqcValid = verify(
- null, signed.message,
- signed.publicKeys.mlDsa65, signed.signatures.mlDsa65
+ null,
+ signed.message,
+ signed.publicKeys.mlDsa65,
+ signed.signatures.mlDsa65,
);
// Both must pass
@@ -281,7 +348,7 @@ import {
createCipheriv,
createDecipheriv,
createHash,
- randomBytes
+ randomBytes,
} from 'react-native-quick-crypto';
// Server publishes its ML-KEM public key
diff --git a/docs/content/docs/api/subtle.mdx b/docs/content/docs/api/subtle.mdx
index 6fdd69fb..5bfeb51b 100644
--- a/docs/content/docs/api/subtle.mdx
+++ b/docs/content/docs/api/subtle.mdx
@@ -35,9 +35,12 @@ Encrypts data.
@@ -51,7 +54,7 @@ Decrypts data. Supports the same algorithms as `encrypt`.
Generates a digital signature.
-**Supported algorithms:** `ECDSA`, `Ed25519`, `Ed448`, `HMAC`, `KMAC128`, `KMAC256`, `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87`, `RSA-PSS`, `RSASSA-PKCS1-v1_5`
+**Supported algorithms:** `ECDSA`, `Ed25519`, `Ed448`, `HMAC`, `KMAC128`, `KMAC256`, `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87`, `RSA-PSS`, `RSASSA-PKCS1-v1_5`, `SLH-DSA-SHA2-{128,192,256}{s,f}`, `SLH-DSA-SHAKE-{128,192,256}{s,f}`
### verify(algorithm, key, signature, data)
@@ -64,7 +67,9 @@ Generates a hash digest.
**Supported algorithms:** `SHA-1`, `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`, `cSHAKE128`, `cSHAKE256`
- `cSHAKE128` and `cSHAKE256` provide SHAKE XOF (Extendable Output Function) support. The `length` parameter (in bytes) is required to specify the output length.
+ `cSHAKE128` and `cSHAKE256` provide SHAKE XOF (Extendable Output Function)
+ support. The `length` parameter (in bytes) is required to specify the output
+ length.
```ts
@@ -72,10 +77,7 @@ Generates a hash digest.
const hash = await subtle.digest('SHA3-256', data);
// cSHAKE256 with custom output length
-const xof = await subtle.digest(
- { name: 'cSHAKE256', length: 64 },
- data
-);
+const xof = await subtle.digest({ name: 'cSHAKE256', length: 64 }, data);
```
### generateKey(algorithm, extractable, keyUsages)
@@ -87,13 +89,13 @@ Generates a new key or key pair.
```ts
const keyPair = await subtle.generateKey(
{
- name: "RSA-OAEP",
+ name: 'RSA-OAEP',
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
- hash: "SHA-256"
+ hash: 'SHA-256',
},
true,
- ["encrypt", "decrypt"]
+ ['encrypt', 'decrypt'],
);
```
@@ -113,9 +115,15 @@ Derives raw bits from a key using a key derivation algorithm.
@@ -128,7 +136,7 @@ const passwordKey = await subtle.importKey(
new TextEncoder().encode('password'),
'PBKDF2',
false,
- ['deriveBits']
+ ['deriveBits'],
);
const bits = await subtle.deriveBits(
@@ -136,47 +144,41 @@ const bits = await subtle.deriveBits(
name: 'PBKDF2',
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
- hash: 'SHA-256'
+ hash: 'SHA-256',
},
passwordKey,
- 256
+ 256,
);
```
```ts
// HKDF
-const ikmKey = await subtle.importKey(
- 'raw',
- masterSecret,
- 'HKDF',
- false,
- ['deriveBits']
-);
+const ikmKey = await subtle.importKey('raw', masterSecret, 'HKDF', false, [
+ 'deriveBits',
+]);
const derived = await subtle.deriveBits(
{
name: 'HKDF',
hash: 'SHA-256',
salt: new Uint8Array(32),
- info: new TextEncoder().encode('session-key')
+ info: new TextEncoder().encode('session-key'),
},
ikmKey,
- 256
+ 256,
);
```
```ts
// X25519 ECDH
-const aliceKey = await subtle.generateKey(
- { name: 'X25519' },
- true,
- ['deriveBits']
-);
+const aliceKey = await subtle.generateKey({ name: 'X25519' }, true, [
+ 'deriveBits',
+]);
const sharedBits = await subtle.deriveBits(
{ name: 'X25519', public: bobPublicKey },
aliceKey.privateKey,
- 256
+ 256,
);
```
@@ -191,12 +193,12 @@ const aesKey = await subtle.deriveKey(
name: 'PBKDF2',
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
- hash: 'SHA-256'
+ hash: 'SHA-256',
},
passwordKey,
{ name: 'AES-GCM', length: 256 },
true,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
);
```
@@ -208,10 +210,16 @@ Exports a key and encrypts (wraps) it for safe transport.
@@ -222,21 +230,18 @@ Exports a key and encrypts (wraps) it for safe transport.
const wrappingKey = await subtle.generateKey(
{ name: 'AES-KW', length: 256 },
true,
- ['wrapKey', 'unwrapKey']
+ ['wrapKey', 'unwrapKey'],
);
const keyToWrap = await subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
);
-const wrapped = await subtle.wrapKey(
- 'raw',
- keyToWrap,
- wrappingKey,
- { name: 'AES-KW' }
-);
+const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, {
+ name: 'AES-KW',
+});
```
### unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)
@@ -251,7 +256,7 @@ const unwrapped = await subtle.unwrapKey(
{ name: 'AES-KW' },
{ name: 'AES-GCM', length: 256 },
true,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
);
```
@@ -263,13 +268,10 @@ Extracts the public key from a private `CryptoKey`.
const keyPair = await subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
- ['sign', 'verify']
+ ['sign', 'verify'],
);
-const publicOnly = await subtle.getPublicKey(
- keyPair.privateKey,
- ['verify']
-);
+const publicOnly = await subtle.getPublicKey(keyPair.privateKey, ['verify']);
```
### SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])
@@ -296,48 +298,49 @@ Post-quantum key encapsulation methods for ML-KEM. See the [PQC page](/docs/api/
### Encryption/Decryption
-| Algorithm | Type | Notes |
-|:----------|:-----|:------|
-| `AES-CBC` | Symmetric | Block cipher, requires padding |
-| `AES-CTR` | Symmetric | Stream-like, counter mode |
-| `AES-GCM` | Symmetric | **Recommended** — authenticated encryption |
-| `AES-OCB` | Symmetric | Authenticated, faster than GCM |
-| `ChaCha20-Poly1305` | Symmetric | Authenticated, fast on mobile |
-| `RSA-OAEP` | Asymmetric | For small payloads or key wrapping |
+| Algorithm | Type | Notes |
+| :------------------ | :--------- | :----------------------------------------- |
+| `AES-CBC` | Symmetric | Block cipher, requires padding |
+| `AES-CTR` | Symmetric | Stream-like, counter mode |
+| `AES-GCM` | Symmetric | **Recommended** — authenticated encryption |
+| `AES-OCB` | Symmetric | Authenticated, faster than GCM |
+| `ChaCha20-Poly1305` | Symmetric | Authenticated, fast on mobile |
+| `RSA-OAEP` | Asymmetric | For small payloads or key wrapping |
### Signing/Verification
-| Algorithm | Type | Notes |
-|:----------|:-----|:------|
-| `ECDSA` | Asymmetric | P-256, P-384, P-521, secp256k1 |
-| `Ed25519` | Asymmetric | Fast, deterministic |
-| `Ed448` | Asymmetric | Higher security Ed curve |
-| `HMAC` | Symmetric | Message authentication |
-| `KMAC128` / `KMAC256` | Symmetric | Keccak-based MAC |
-| `ML-DSA-44/65/87` | Post-Quantum | FIPS 204 lattice signatures |
-| `RSA-PSS` | Asymmetric | Probabilistic RSA signatures |
-| `RSASSA-PKCS1-v1_5` | Asymmetric | Legacy RSA signatures |
+| Algorithm | Type | Notes |
+| :---------------------------------------- | :----------- | :------------------------------------------- |
+| `ECDSA` | Asymmetric | P-256, P-384, P-521, secp256k1 |
+| `Ed25519` | Asymmetric | Fast, deterministic |
+| `Ed448` | Asymmetric | Higher security Ed curve |
+| `HMAC` | Symmetric | Message authentication |
+| `KMAC128` / `KMAC256` | Symmetric | Keccak-based MAC |
+| `ML-DSA-44/65/87` | Post-Quantum | FIPS 204 lattice signatures |
+| `RSA-PSS` | Asymmetric | Probabilistic RSA signatures |
+| `RSASSA-PKCS1-v1_5` | Asymmetric | Legacy RSA signatures |
+| `SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` | Post-Quantum | FIPS 205 hash-based signatures (12 variants) |
### Key Derivation
-| Algorithm | Input | Notes |
-|:----------|:------|:------|
-| `PBKDF2` | Password | Iterations-based, widely supported |
-| `HKDF` | Key material | Extract-and-Expand (RFC 5869) |
-| `Argon2d/i/id` | Password | Memory-hard, PHC winner |
-| `ECDH` | Key pair | P-256, P-384, P-521, secp256k1 |
-| `X25519` | Key pair | Modern ECDH |
-| `X448` | Key pair | Higher security ECDH |
+| Algorithm | Input | Notes |
+| :------------- | :----------- | :--------------------------------- |
+| `PBKDF2` | Password | Iterations-based, widely supported |
+| `HKDF` | Key material | Extract-and-Expand (RFC 5869) |
+| `Argon2d/i/id` | Password | Memory-hard, PHC winner |
+| `ECDH` | Key pair | P-256, P-384, P-521, secp256k1 |
+| `X25519` | Key pair | Modern ECDH |
+| `X448` | Key pair | Higher security ECDH |
### Key Wrapping
-| Algorithm | Notes |
-|:----------|:------|
-| `AES-KW` | RFC 3394, purpose-built key wrapping |
-| `AES-GCM` | Authenticated wrapping |
-| `AES-CBC` / `AES-CTR` / `AES-OCB` | Alternative wrapping |
-| `ChaCha20-Poly1305` | Authenticated wrapping |
-| `RSA-OAEP` | Asymmetric wrapping |
+| Algorithm | Notes |
+| :-------------------------------- | :----------------------------------- |
+| `AES-KW` | RFC 3394, purpose-built key wrapping |
+| `AES-GCM` | Authenticated wrapping |
+| `AES-CBC` / `AES-CTR` / `AES-OCB` | Alternative wrapping |
+| `ChaCha20-Poly1305` | Authenticated wrapping |
+| `RSA-OAEP` | Asymmetric wrapping |
---
@@ -351,24 +354,23 @@ Symmetric encryption with a random key.
import { subtle } from 'react-native-quick-crypto';
async function secureMessage(msg: string) {
- // Generate Key
- const key = await subtle.generateKey(
- { name: "AES-GCM", length: 256 },
- true,
- ["encrypt", "decrypt"]
- );
-
- // Encrypt
- const iv = QuickCrypto.getRandomValues(new Uint8Array(12));
- const encoded = new TextEncoder().encode(msg);
-
- const ciphertext = await subtle.encrypt(
- { name: "AES-GCM", iv: iv },
- key,
- encoded
- );
-
- return { key, iv, ciphertext };
+ // Generate Key
+ const key = await subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
+ 'encrypt',
+ 'decrypt',
+ ]);
+
+ // Encrypt
+ const iv = QuickCrypto.getRandomValues(new Uint8Array(12));
+ const encoded = new TextEncoder().encode(msg);
+
+ const ciphertext = await subtle.encrypt(
+ { name: 'AES-GCM', iv: iv },
+ key,
+ encoded,
+ );
+
+ return { key, iv, ciphertext };
}
```
@@ -378,24 +380,24 @@ Importing a public key from a JWK to verify a token.
```ts
async function verifyToken(token: string, jwk: any) {
- const pubKey = await subtle.importKey(
- "jwk",
- jwk,
- { name: "ECDSA", namedCurve: "P-256" },
- false,
- ["verify"]
- );
-
- const [header, payload, sigBase64] = token.split('.');
- const data = new TextEncoder().encode(`${header}.${payload}`);
- const signature = Buffer.from(sigBase64, 'base64');
-
- return await subtle.verify(
- { name: "ECDSA", hash: "SHA-256" },
- pubKey,
- signature,
- data
- );
+ const pubKey = await subtle.importKey(
+ 'jwk',
+ jwk,
+ { name: 'ECDSA', namedCurve: 'P-256' },
+ false,
+ ['verify'],
+ );
+
+ const [header, payload, sigBase64] = token.split('.');
+ const data = new TextEncoder().encode(`${header}.${payload}`);
+ const signature = Buffer.from(sigBase64, 'base64');
+
+ return await subtle.verify(
+ { name: 'ECDSA', hash: 'SHA-256' },
+ pubKey,
+ signature,
+ data,
+ );
}
```
@@ -413,7 +415,7 @@ async function encryptWithPassword(plaintext: string, password: string) {
new TextEncoder().encode(password),
'PBKDF2',
false,
- ['deriveKey']
+ ['deriveKey'],
);
const aesKey = await subtle.deriveKey(
@@ -421,13 +423,13 @@ async function encryptWithPassword(plaintext: string, password: string) {
passwordKey,
{ name: 'AES-GCM', length: 256 },
false,
- ['encrypt', 'decrypt']
+ ['encrypt', 'decrypt'],
);
const ciphertext = await subtle.encrypt(
{ name: 'AES-GCM', iv },
aesKey,
- new TextEncoder().encode(plaintext)
+ new TextEncoder().encode(plaintext),
);
return { ciphertext, salt, iv };
diff --git a/example/src/tests/subtle/sign_verify.ts b/example/src/tests/subtle/sign_verify.ts
index 3a6ea388..4ee5fd7f 100644
--- a/example/src/tests/subtle/sign_verify.ts
+++ b/example/src/tests/subtle/sign_verify.ts
@@ -942,6 +942,199 @@ test(SUITE, 'ML-DSA cross-variant: 44 sig under 65 pub rejected', async () => {
}
});
+// --- SLH-DSA Tests (FIPS 205) ---
+//
+// SLH-DSA signature sizes (bytes), per FIPS 205 §11:
+// 128s: 7856 128f: 17088
+// 192s: 16224 192f: 35664
+// 256s: 29792 256f: 49856
+
+type SlhDsaVariant =
+ | 'SLH-DSA-SHA2-128s'
+ | 'SLH-DSA-SHA2-128f'
+ | 'SLH-DSA-SHA2-192s'
+ | 'SLH-DSA-SHA2-192f'
+ | 'SLH-DSA-SHA2-256s'
+ | 'SLH-DSA-SHA2-256f'
+ | 'SLH-DSA-SHAKE-128s'
+ | 'SLH-DSA-SHAKE-128f'
+ | 'SLH-DSA-SHAKE-192s'
+ | 'SLH-DSA-SHAKE-192f'
+ | 'SLH-DSA-SHAKE-256s'
+ | 'SLH-DSA-SHAKE-256f';
+
+const SLHDSA_SIGNATURE_SIZES: Record = {
+ 'SLH-DSA-SHA2-128s': 7856,
+ 'SLH-DSA-SHA2-128f': 17088,
+ 'SLH-DSA-SHA2-192s': 16224,
+ 'SLH-DSA-SHA2-192f': 35664,
+ 'SLH-DSA-SHA2-256s': 29792,
+ 'SLH-DSA-SHA2-256f': 49856,
+ 'SLH-DSA-SHAKE-128s': 7856,
+ 'SLH-DSA-SHAKE-128f': 17088,
+ 'SLH-DSA-SHAKE-192s': 16224,
+ 'SLH-DSA-SHAKE-192f': 35664,
+ 'SLH-DSA-SHAKE-256s': 29792,
+ 'SLH-DSA-SHAKE-256f': 49856,
+};
+
+// SLH-DSA keygen for the larger parameter sets is fast, but signing —
+// especially the `s` (small-signature) variants — is significantly slower
+// than ML-DSA. Tests are intentionally minimal per variant: one round-trip
+// each, plus a single tampered-signature check on the smallest variant to
+// guard the negative path.
+const SLHDSA_VARIANTS: SlhDsaVariant[] = [
+ 'SLH-DSA-SHA2-128s',
+ 'SLH-DSA-SHA2-128f',
+ 'SLH-DSA-SHA2-192s',
+ 'SLH-DSA-SHA2-192f',
+ 'SLH-DSA-SHA2-256s',
+ 'SLH-DSA-SHA2-256f',
+ 'SLH-DSA-SHAKE-128s',
+ 'SLH-DSA-SHAKE-128f',
+ 'SLH-DSA-SHAKE-192s',
+ 'SLH-DSA-SHAKE-192f',
+ 'SLH-DSA-SHAKE-256s',
+ 'SLH-DSA-SHAKE-256f',
+];
+
+for (const variant of SLHDSA_VARIANTS) {
+ test(SUITE, `${variant} sign/verify`, async () => {
+ const keyPair = await generateKeyPairChecked({ name: variant }, true, [
+ 'sign',
+ 'verify',
+ ]);
+
+ const signature = await subtle.sign(
+ { name: variant },
+ keyPair.privateKey,
+ testData,
+ );
+
+ expect(signature.byteLength).to.equal(SLHDSA_SIGNATURE_SIZES[variant]);
+
+ const isValid = await subtle.verify(
+ { name: variant },
+ keyPair.publicKey,
+ signature,
+ testData,
+ );
+
+ expect(isValid).to.equal(true);
+ });
+
+ test(SUITE, `${variant} export → import → sign+verify`, async () => {
+ const original = await generateKeyPairChecked({ name: variant }, true, [
+ 'sign',
+ 'verify',
+ ]);
+
+ const pkcs8 = (await subtle.exportKey(
+ 'pkcs8',
+ original.privateKey,
+ )) as ArrayBuffer;
+ const spki = (await subtle.exportKey(
+ 'spki',
+ original.publicKey,
+ )) as ArrayBuffer;
+
+ const importedPriv = await subtle.importKey(
+ 'pkcs8',
+ pkcs8,
+ { name: variant },
+ true,
+ ['sign'],
+ );
+ const importedPub = await subtle.importKey(
+ 'spki',
+ spki,
+ { name: variant },
+ true,
+ ['verify'],
+ );
+
+ const sig = await subtle.sign({ name: variant }, importedPriv, testData);
+ expect(sig.byteLength).to.equal(SLHDSA_SIGNATURE_SIZES[variant]);
+
+ const ok = await subtle.verify(
+ { name: variant },
+ importedPub,
+ sig,
+ testData,
+ );
+ expect(ok).to.equal(true);
+ });
+}
+
+test(SUITE, 'SLH-DSA-SHA2-128f verify rejects tampered signature', async () => {
+ const variant: SlhDsaVariant = 'SLH-DSA-SHA2-128f';
+ const keyPair = await generateKeyPairChecked({ name: variant }, true, [
+ 'sign',
+ 'verify',
+ ]);
+
+ const signature = await subtle.sign(
+ { name: variant },
+ keyPair.privateKey,
+ testData,
+ );
+
+ const tampered = new Uint8Array(signature);
+ tampered[0] = (tampered[0] ?? 0) ^ 0xff;
+
+ const ok = await subtle.verify(
+ { name: variant },
+ keyPair.publicKey,
+ tampered,
+ testData,
+ );
+ expect(ok).to.equal(false);
+});
+
+// Cross-variant: an SLH-DSA-SHA2-128f signature must not verify under an
+// SLH-DSA-SHAKE-128f public key — the parameter sets share signature length
+// but use different hash families and unrelated keys.
+test(
+ SUITE,
+ 'SLH-DSA cross-variant: sha2-128f sig under shake-128f pub rejected',
+ async () => {
+ const kpSha2 = await generateKeyPairChecked(
+ { name: 'SLH-DSA-SHA2-128f' },
+ true,
+ ['sign', 'verify'],
+ );
+ const kpShake = await generateKeyPairChecked(
+ { name: 'SLH-DSA-SHAKE-128f' },
+ true,
+ ['sign', 'verify'],
+ );
+
+ const sig = await subtle.sign(
+ { name: 'SLH-DSA-SHA2-128f' },
+ kpSha2.privateKey,
+ testData,
+ );
+
+ let result: boolean | Error;
+ try {
+ result = await subtle.verify(
+ { name: 'SLH-DSA-SHAKE-128f' },
+ kpShake.publicKey,
+ sig,
+ testData,
+ );
+ } catch (e) {
+ result = e as Error;
+ }
+
+ if (typeof result === 'boolean') {
+ expect(result).to.equal(false);
+ } else {
+ expect(result).to.be.instanceOf(Error);
+ }
+ },
+);
+
// --- Key Import/Export and Sign/Verify ---
test(SUITE, 'Sign with imported Ed25519 key', async () => {
diff --git a/packages/react-native-quick-crypto/android/CMakeLists.txt b/packages/react-native-quick-crypto/android/CMakeLists.txt
index 948c0d65..fe293980 100644
--- a/packages/react-native-quick-crypto/android/CMakeLists.txt
+++ b/packages/react-native-quick-crypto/android/CMakeLists.txt
@@ -52,6 +52,7 @@ add_library(
../cpp/keys/KeyObjectData.cpp
../cpp/mldsa/HybridMlDsaKeyPair.cpp
../cpp/mlkem/HybridMlKemKeyPair.cpp
+ ../cpp/slhdsa/HybridSlhDsaKeyPair.cpp
../cpp/pbkdf2/HybridPbkdf2.cpp
../cpp/prime/HybridPrime.cpp
../cpp/random/HybridRandom.cpp
@@ -92,6 +93,7 @@ include_directories(
"../cpp/keys"
"../cpp/mldsa"
"../cpp/mlkem"
+ "../cpp/slhdsa"
"../cpp/pbkdf2"
"../cpp/prime"
"../cpp/random"
diff --git a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp
index 86eb18e1..f38b3739 100644
--- a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp
+++ b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp
@@ -30,6 +30,7 @@ static void configurePqcOutputFormats() {
[](OSSL_PROVIDER* provider, void*) -> int {
OSSL_PROVIDER_add_conf_parameter(provider, "ml-kem.output_formats", "seed-only,priv-only");
OSSL_PROVIDER_add_conf_parameter(provider, "ml-dsa.output_formats", "seed-only,priv-only");
+ OSSL_PROVIDER_add_conf_parameter(provider, "slh-dsa.output_formats", "seed-only,priv-only");
return 1;
},
nullptr);
@@ -174,7 +175,7 @@ std::shared_ptr HybridKeyObjectHandle::exportKey(std::optional= 0x30500000L
- // EVP_PKEY_id returns -1 for provider-only key types (e.g. ML-KEM)
+ // EVP_PKEY_id returns -1 for provider-only key types (e.g. ML-KEM, SLH-DSA)
// Fall back to string-based type name comparison
const char* typeName = EVP_PKEY_get0_type_name(pkey.get());
if (typeName != nullptr) {
@@ -481,6 +482,30 @@ AsymmetricKeyType HybridKeyObjectHandle::getAsymmetricKeyType() {
return AsymmetricKeyType::ML_KEM_768;
if (name == "ML-KEM-1024")
return AsymmetricKeyType::ML_KEM_1024;
+ if (name == "SLH-DSA-SHA2-128s")
+ return AsymmetricKeyType::SLH_DSA_SHA2_128S;
+ if (name == "SLH-DSA-SHA2-128f")
+ return AsymmetricKeyType::SLH_DSA_SHA2_128F;
+ if (name == "SLH-DSA-SHA2-192s")
+ return AsymmetricKeyType::SLH_DSA_SHA2_192S;
+ if (name == "SLH-DSA-SHA2-192f")
+ return AsymmetricKeyType::SLH_DSA_SHA2_192F;
+ if (name == "SLH-DSA-SHA2-256s")
+ return AsymmetricKeyType::SLH_DSA_SHA2_256S;
+ if (name == "SLH-DSA-SHA2-256f")
+ return AsymmetricKeyType::SLH_DSA_SHA2_256F;
+ if (name == "SLH-DSA-SHAKE-128s")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_128S;
+ if (name == "SLH-DSA-SHAKE-128f")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_128F;
+ if (name == "SLH-DSA-SHAKE-192s")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_192S;
+ if (name == "SLH-DSA-SHAKE-192f")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_192F;
+ if (name == "SLH-DSA-SHAKE-256s")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_256S;
+ if (name == "SLH-DSA-SHAKE-256f")
+ return AsymmetricKeyType::SLH_DSA_SHAKE_256F;
}
#endif
@@ -790,6 +815,30 @@ std::optional HybridKeyObjectHandle::initJwk(const JWK& keyData, std::o
nid = EVP_PKEY_ML_KEM_768;
else if (alg == "ML-KEM-1024")
nid = EVP_PKEY_ML_KEM_1024;
+ else if (alg == "SLH-DSA-SHA2-128s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_128S;
+ else if (alg == "SLH-DSA-SHA2-128f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_128F;
+ else if (alg == "SLH-DSA-SHA2-192s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_192S;
+ else if (alg == "SLH-DSA-SHA2-192f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_192F;
+ else if (alg == "SLH-DSA-SHA2-256s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_256S;
+ else if (alg == "SLH-DSA-SHA2-256f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_256F;
+ else if (alg == "SLH-DSA-SHAKE-128s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_128S;
+ else if (alg == "SLH-DSA-SHAKE-128f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_128F;
+ else if (alg == "SLH-DSA-SHAKE-192s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_192S;
+ else if (alg == "SLH-DSA-SHAKE-192f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_192F;
+ else if (alg == "SLH-DSA-SHAKE-256s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
+ else if (alg == "SLH-DSA-SHAKE-256f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_256F;
else
throw std::runtime_error("Unsupported JWK AKP \"alg\": " + alg);
@@ -964,6 +1013,30 @@ bool HybridKeyObjectHandle::initPqcRaw(const std::string& algorithmName, const s
nid = EVP_PKEY_ML_DSA_65;
else if (algorithmName == "ML-DSA-87")
nid = EVP_PKEY_ML_DSA_87;
+ else if (algorithmName == "SLH-DSA-SHA2-128s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_128S;
+ else if (algorithmName == "SLH-DSA-SHA2-128f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_128F;
+ else if (algorithmName == "SLH-DSA-SHA2-192s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_192S;
+ else if (algorithmName == "SLH-DSA-SHA2-192f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_192F;
+ else if (algorithmName == "SLH-DSA-SHA2-256s")
+ nid = EVP_PKEY_SLH_DSA_SHA2_256S;
+ else if (algorithmName == "SLH-DSA-SHA2-256f")
+ nid = EVP_PKEY_SLH_DSA_SHA2_256F;
+ else if (algorithmName == "SLH-DSA-SHAKE-128s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_128S;
+ else if (algorithmName == "SLH-DSA-SHAKE-128f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_128F;
+ else if (algorithmName == "SLH-DSA-SHAKE-192s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_192S;
+ else if (algorithmName == "SLH-DSA-SHAKE-192f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_192F;
+ else if (algorithmName == "SLH-DSA-SHAKE-256s")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
+ else if (algorithmName == "SLH-DSA-SHAKE-256f")
+ nid = EVP_PKEY_SLH_DSA_SHAKE_256F;
else
throw std::runtime_error("Unknown PQC algorithm: " + algorithmName);
diff --git a/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp b/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp
index f8c88a3c..13d57c21 100644
--- a/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp
+++ b/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp
@@ -73,12 +73,32 @@ void HybridSignHandle::update(const std::shared_ptr& data) {
}
}
-// Check if key type requires one-shot signing (Ed25519, Ed448, ML-DSA)
+// Check if key type requires one-shot signing (Ed25519, Ed448, ML-DSA, SLH-DSA)
static bool isOneShotVariant(EVP_PKEY* pkey) {
int type = EVP_PKEY_id(pkey);
#if RNQC_HAS_ML_DSA
- return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448 || type == EVP_PKEY_ML_DSA_44 || type == EVP_PKEY_ML_DSA_65 ||
- type == EVP_PKEY_ML_DSA_87;
+ switch (type) {
+ case EVP_PKEY_ED25519:
+ case EVP_PKEY_ED448:
+ case EVP_PKEY_ML_DSA_44:
+ case EVP_PKEY_ML_DSA_65:
+ case EVP_PKEY_ML_DSA_87:
+ case EVP_PKEY_SLH_DSA_SHA2_128S:
+ case EVP_PKEY_SLH_DSA_SHA2_128F:
+ case EVP_PKEY_SLH_DSA_SHA2_192S:
+ case EVP_PKEY_SLH_DSA_SHA2_192F:
+ case EVP_PKEY_SLH_DSA_SHA2_256S:
+ case EVP_PKEY_SLH_DSA_SHA2_256F:
+ case EVP_PKEY_SLH_DSA_SHAKE_128S:
+ case EVP_PKEY_SLH_DSA_SHAKE_128F:
+ case EVP_PKEY_SLH_DSA_SHAKE_192S:
+ case EVP_PKEY_SLH_DSA_SHAKE_192F:
+ case EVP_PKEY_SLH_DSA_SHAKE_256S:
+ case EVP_PKEY_SLH_DSA_SHAKE_256F:
+ return true;
+ default:
+ return false;
+ }
#else
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448;
#endif
diff --git a/packages/react-native-quick-crypto/cpp/sign/HybridVerifyHandle.cpp b/packages/react-native-quick-crypto/cpp/sign/HybridVerifyHandle.cpp
index 52739353..18fce058 100644
--- a/packages/react-native-quick-crypto/cpp/sign/HybridVerifyHandle.cpp
+++ b/packages/react-native-quick-crypto/cpp/sign/HybridVerifyHandle.cpp
@@ -73,12 +73,32 @@ void HybridVerifyHandle::update(const std::shared_ptr& data) {
}
}
-// Check if key type requires one-shot verification (Ed25519, Ed448, ML-DSA)
+// Check if key type requires one-shot verification (Ed25519, Ed448, ML-DSA, SLH-DSA)
static bool isOneShotVariant(EVP_PKEY* pkey) {
int type = EVP_PKEY_id(pkey);
#if RNQC_HAS_ML_DSA
- return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448 || type == EVP_PKEY_ML_DSA_44 || type == EVP_PKEY_ML_DSA_65 ||
- type == EVP_PKEY_ML_DSA_87;
+ switch (type) {
+ case EVP_PKEY_ED25519:
+ case EVP_PKEY_ED448:
+ case EVP_PKEY_ML_DSA_44:
+ case EVP_PKEY_ML_DSA_65:
+ case EVP_PKEY_ML_DSA_87:
+ case EVP_PKEY_SLH_DSA_SHA2_128S:
+ case EVP_PKEY_SLH_DSA_SHA2_128F:
+ case EVP_PKEY_SLH_DSA_SHA2_192S:
+ case EVP_PKEY_SLH_DSA_SHA2_192F:
+ case EVP_PKEY_SLH_DSA_SHA2_256S:
+ case EVP_PKEY_SLH_DSA_SHA2_256F:
+ case EVP_PKEY_SLH_DSA_SHAKE_128S:
+ case EVP_PKEY_SLH_DSA_SHAKE_128F:
+ case EVP_PKEY_SLH_DSA_SHAKE_192S:
+ case EVP_PKEY_SLH_DSA_SHAKE_192F:
+ case EVP_PKEY_SLH_DSA_SHAKE_256S:
+ case EVP_PKEY_SLH_DSA_SHAKE_256F:
+ return true;
+ default:
+ return false;
+ }
#else
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448;
#endif
diff --git a/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.cpp b/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.cpp
new file mode 100644
index 00000000..ef9db32d
--- /dev/null
+++ b/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.cpp
@@ -0,0 +1,245 @@
+#include "HybridSlhDsaKeyPair.hpp"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "QuickCryptoUtils.hpp"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30500000L
+#define RNQC_HAS_SLH_DSA 1
+#else
+#define RNQC_HAS_SLH_DSA 0
+#endif
+
+namespace margelo::nitro::crypto {
+
+using EVP_MD_CTX_ptr = std::unique_ptr;
+using EVP_PKEY_CTX_ptr = std::unique_ptr;
+
+#if RNQC_HAS_SLH_DSA
+static constexpr std::array kSlhDsaVariants{
+ "SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f", "SLH-DSA-SHA2-192s", "SLH-DSA-SHA2-192f", "SLH-DSA-SHA2-256s", "SLH-DSA-SHA2-256f",
+ "SLH-DSA-SHAKE-128s", "SLH-DSA-SHAKE-128f", "SLH-DSA-SHAKE-192s", "SLH-DSA-SHAKE-192f", "SLH-DSA-SHAKE-256s", "SLH-DSA-SHAKE-256f",
+};
+
+static bool isValidSlhDsaVariant(const std::string& variant) {
+ for (const char* v : kSlhDsaVariants) {
+ if (variant == v) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void HybridSlhDsaKeyPair::setVariant(const std::string& variant) {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ if (!isValidSlhDsaVariant(variant)) {
+ throw std::runtime_error("Invalid SLH-DSA variant: " + variant);
+ }
+ variant_ = variant;
+#endif
+}
+
+std::shared_ptr> HybridSlhDsaKeyPair::generateKeyPair(double publicFormat, double publicType, double privateFormat,
+ double privateType) {
+ auto self = this->shared_cast();
+ return Promise::async([self, publicFormat, publicType, privateFormat, privateType]() {
+ self->generateKeyPairSync(publicFormat, publicType, privateFormat, privateType);
+ });
+}
+
+void HybridSlhDsaKeyPair::generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ clearOpenSSLErrors();
+
+ if (variant_.empty()) {
+ throw std::runtime_error("SLH-DSA variant not set. Call setVariant() first.");
+ }
+
+ publicFormat_ = static_cast(publicFormat);
+ publicType_ = static_cast(publicType);
+ privateFormat_ = static_cast(privateFormat);
+ privateType_ = static_cast(privateType);
+
+ pkey_.reset();
+
+ EVP_PKEY_CTX_ptr pctx(EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr), EVP_PKEY_CTX_free);
+ if (pctx == nullptr) {
+ throw std::runtime_error("Failed to create key context for " + variant_ + ": " + getOpenSSLError());
+ }
+
+ if (EVP_PKEY_keygen_init(pctx.get()) <= 0) {
+ throw std::runtime_error("Failed to initialize keygen: " + getOpenSSLError());
+ }
+
+ EVP_PKEY* raw = nullptr;
+ if (EVP_PKEY_keygen(pctx.get(), &raw) <= 0) {
+ throw std::runtime_error("Failed to generate SLH-DSA key pair: " + getOpenSSLError());
+ }
+ pkey_.reset(raw);
+#endif
+}
+
+std::shared_ptr HybridSlhDsaKeyPair::getPublicKey() {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ checkKeyPair();
+
+ BIO* bio = BIO_new(BIO_s_mem());
+ if (!bio) {
+ throw std::runtime_error("Failed to create BIO for public key export");
+ }
+
+ int result;
+ if (publicFormat_ == 1) {
+ result = PEM_write_bio_PUBKEY(bio, pkey_.get());
+ } else {
+ result = i2d_PUBKEY_bio(bio, pkey_.get());
+ }
+
+ if (result != 1) {
+ BIO_free(bio);
+ throw std::runtime_error("Failed to export public key: " + getOpenSSLError());
+ }
+
+ BUF_MEM* bptr;
+ BIO_get_mem_ptr(bio, &bptr);
+
+ size_t len = bptr->length;
+ auto buf = std::make_unique(len);
+ memcpy(buf.get(), bptr->data, len);
+
+ BIO_free(bio);
+
+ uint8_t* raw_ptr = buf.get();
+ return std::make_shared(buf.release(), len, [raw_ptr]() { delete[] raw_ptr; });
+#endif
+}
+
+std::shared_ptr HybridSlhDsaKeyPair::getPrivateKey() {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ checkKeyPair();
+
+ BIO* bio = BIO_new(BIO_s_mem());
+ if (!bio) {
+ throw std::runtime_error("Failed to create BIO for private key export");
+ }
+
+ int result;
+ if (privateFormat_ == 1) {
+ result = PEM_write_bio_PrivateKey(bio, pkey_.get(), nullptr, nullptr, 0, nullptr, nullptr);
+ } else {
+ result = i2d_PKCS8PrivateKey_bio(bio, pkey_.get(), nullptr, nullptr, 0, nullptr, nullptr);
+ }
+
+ if (result != 1) {
+ BIO_free(bio);
+ throw std::runtime_error("Failed to export private key: " + getOpenSSLError());
+ }
+
+ BUF_MEM* bptr;
+ BIO_get_mem_ptr(bio, &bptr);
+
+ size_t len = bptr->length;
+ auto buf = std::make_unique(len);
+ memcpy(buf.get(), bptr->data, len);
+
+ secureZero(bptr->data, bptr->length);
+ BIO_free(bio);
+
+ uint8_t* raw_ptr = buf.get();
+ return std::make_shared(buf.release(), len, [raw_ptr]() { delete[] raw_ptr; });
+#endif
+}
+
+std::shared_ptr>> HybridSlhDsaKeyPair::sign(const std::shared_ptr& message) {
+ auto nativeMessage = ToNativeArrayBuffer(message);
+ auto self = this->shared_cast();
+ return Promise>::async([self, nativeMessage]() { return self->signSync(nativeMessage); });
+}
+
+std::shared_ptr HybridSlhDsaKeyPair::signSync(const std::shared_ptr& message) {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ clearOpenSSLErrors();
+ checkKeyPair();
+
+ EVP_MD_CTX_ptr md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+ if (md_ctx == nullptr) {
+ throw std::runtime_error("Failed to create signing context");
+ }
+
+ if (EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr, pkey_.get()) <= 0) {
+ throw std::runtime_error("Failed to initialize signing: " + getOpenSSLError());
+ }
+
+ size_t sig_len = 0;
+ if (EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, message->data(), message->size()) <= 0) {
+ throw std::runtime_error("Failed to calculate signature size: " + getOpenSSLError());
+ }
+
+ auto sig = std::make_unique(sig_len);
+
+ if (EVP_DigestSign(md_ctx.get(), sig.get(), &sig_len, message->data(), message->size()) <= 0) {
+ throw std::runtime_error("Failed to sign message: " + getOpenSSLError());
+ }
+
+ uint8_t* raw_ptr = sig.get();
+ return std::make_shared(sig.release(), sig_len, [raw_ptr]() { delete[] raw_ptr; });
+#endif
+}
+
+std::shared_ptr> HybridSlhDsaKeyPair::verify(const std::shared_ptr& signature,
+ const std::shared_ptr& message) {
+ auto nativeSignature = ToNativeArrayBuffer(signature);
+ auto nativeMessage = ToNativeArrayBuffer(message);
+ auto self = this->shared_cast();
+ return Promise::async([self, nativeSignature, nativeMessage]() { return self->verifySync(nativeSignature, nativeMessage); });
+}
+
+bool HybridSlhDsaKeyPair::verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) {
+#if !RNQC_HAS_SLH_DSA
+ throw std::runtime_error("SLH-DSA requires OpenSSL 3.5+");
+#else
+ clearOpenSSLErrors();
+ checkKeyPair();
+
+ EVP_MD_CTX_ptr md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+ if (md_ctx == nullptr) {
+ throw std::runtime_error("Failed to create verify context");
+ }
+
+ if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, nullptr, nullptr, pkey_.get()) <= 0) {
+ throw std::runtime_error("Failed to initialize verification: " + getOpenSSLError());
+ }
+
+ int result = EVP_DigestVerify(md_ctx.get(), signature->data(), signature->size(), message->data(), message->size());
+
+ if (result < 0) {
+ throw std::runtime_error("Verification error: " + getOpenSSLError());
+ }
+
+ return result == 1;
+#endif
+}
+
+void HybridSlhDsaKeyPair::checkKeyPair() {
+ if (!pkey_) {
+ throw std::runtime_error("Key pair not initialized");
+ }
+}
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.hpp b/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.hpp
new file mode 100644
index 00000000..66423835
--- /dev/null
+++ b/packages/react-native-quick-crypto/cpp/slhdsa/HybridSlhDsaKeyPair.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "HybridSlhDsaKeyPairSpec.hpp"
+
+namespace margelo::nitro::crypto {
+
+class HybridSlhDsaKeyPair : public HybridSlhDsaKeyPairSpec {
+ public:
+ HybridSlhDsaKeyPair() : HybridObject(TAG) {}
+ ~HybridSlhDsaKeyPair() override = default;
+
+ std::shared_ptr> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType) override;
+
+ void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) override;
+
+ std::shared_ptr getPublicKey() override;
+ std::shared_ptr getPrivateKey() override;
+
+ std::shared_ptr>> sign(const std::shared_ptr& message) override;
+
+ std::shared_ptr signSync(const std::shared_ptr& message) override;
+
+ std::shared_ptr> verify(const std::shared_ptr& signature,
+ const std::shared_ptr& message) override;
+
+ bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) override;
+
+ void setVariant(const std::string& variant) override;
+
+ private:
+ using EVP_PKEY_ptr = std::unique_ptr;
+
+ std::string variant_;
+ EVP_PKEY_ptr pkey_{nullptr, EVP_PKEY_free};
+
+ int publicFormat_ = -1;
+ int publicType_ = -1;
+ int privateFormat_ = -1;
+ int privateType_ = -1;
+
+ void checkKeyPair();
+};
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/nitro.json b/packages/react-native-quick-crypto/nitro.json
index 16bd5f41..91c49343 100644
--- a/packages/react-native-quick-crypto/nitro.json
+++ b/packages/react-native-quick-crypto/nitro.json
@@ -62,6 +62,9 @@
"MlKemKeyPair": {
"cpp": "HybridMlKemKeyPair"
},
+ "SlhDsaKeyPair": {
+ "cpp": "HybridSlhDsaKeyPair"
+ },
"Pbkdf2": {
"cpp": "HybridPbkdf2"
},
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
index 966c56cd..f15a6a85 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
+++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
@@ -59,6 +59,7 @@ target_sources(
../nitrogen/generated/shared/c++/HybridScryptSpec.cpp
../nitrogen/generated/shared/c++/HybridSignHandleSpec.cpp
../nitrogen/generated/shared/c++/HybridVerifyHandleSpec.cpp
+ ../nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.cpp
../nitrogen/generated/shared/c++/HybridUtilsSpec.cpp
../nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.cpp
# Android-specific Nitrogen C++ sources
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
index 44ef262c..5483c34b 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
+++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
@@ -33,6 +33,7 @@
#include "HybridKeyObjectHandle.hpp"
#include "HybridMlDsaKeyPair.hpp"
#include "HybridMlKemKeyPair.hpp"
+#include "HybridSlhDsaKeyPair.hpp"
#include "HybridPbkdf2.hpp"
#include "HybridPrime.hpp"
#include "HybridRandom.hpp"
@@ -218,6 +219,15 @@ int initialize(JavaVM* vm) {
return std::make_shared();
}
);
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "SlhDsaKeyPair",
+ []() -> std::shared_ptr {
+ static_assert(std::is_default_constructible_v,
+ "The HybridObject \"HybridSlhDsaKeyPair\" is not default-constructible! "
+ "Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
+ return std::make_shared();
+ }
+ );
HybridObjectRegistry::registerHybridObjectConstructor(
"Pbkdf2",
[]() -> std::shared_ptr {
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
index 43237e32..f8f8a507 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
+++ b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
@@ -28,6 +28,7 @@
#include "HybridKeyObjectHandle.hpp"
#include "HybridMlDsaKeyPair.hpp"
#include "HybridMlKemKeyPair.hpp"
+#include "HybridSlhDsaKeyPair.hpp"
#include "HybridPbkdf2.hpp"
#include "HybridPrime.hpp"
#include "HybridRandom.hpp"
@@ -210,6 +211,15 @@ + (void) load {
return std::make_shared();
}
);
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "SlhDsaKeyPair",
+ []() -> std::shared_ptr {
+ static_assert(std::is_default_constructible_v,
+ "The HybridObject \"HybridSlhDsaKeyPair\" is not default-constructible! "
+ "Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
+ return std::make_shared();
+ }
+ );
HybridObjectRegistry::registerHybridObjectConstructor(
"Pbkdf2",
[]() -> std::shared_ptr {
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp
index d9d42ae8..e2349aa9 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp
+++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp
@@ -44,6 +44,18 @@ namespace margelo::nitro::crypto {
ML_KEM_512 SWIFT_NAME(mlKem512) = 12,
ML_KEM_768 SWIFT_NAME(mlKem768) = 13,
ML_KEM_1024 SWIFT_NAME(mlKem1024) = 14,
+ SLH_DSA_SHA2_128S SWIFT_NAME(slhDsaSha2128s) = 15,
+ SLH_DSA_SHA2_128F SWIFT_NAME(slhDsaSha2128f) = 16,
+ SLH_DSA_SHA2_192S SWIFT_NAME(slhDsaSha2192s) = 17,
+ SLH_DSA_SHA2_192F SWIFT_NAME(slhDsaSha2192f) = 18,
+ SLH_DSA_SHA2_256S SWIFT_NAME(slhDsaSha2256s) = 19,
+ SLH_DSA_SHA2_256F SWIFT_NAME(slhDsaSha2256f) = 20,
+ SLH_DSA_SHAKE_128S SWIFT_NAME(slhDsaShake128s) = 21,
+ SLH_DSA_SHAKE_128F SWIFT_NAME(slhDsaShake128f) = 22,
+ SLH_DSA_SHAKE_192S SWIFT_NAME(slhDsaShake192s) = 23,
+ SLH_DSA_SHAKE_192F SWIFT_NAME(slhDsaShake192f) = 24,
+ SLH_DSA_SHAKE_256S SWIFT_NAME(slhDsaShake256s) = 25,
+ SLH_DSA_SHAKE_256F SWIFT_NAME(slhDsaShake256f) = 26,
} CLOSED_ENUM;
} // namespace margelo::nitro::crypto
@@ -71,6 +83,18 @@ namespace margelo::nitro {
case hashString("ml-kem-512"): return margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_512;
case hashString("ml-kem-768"): return margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_768;
case hashString("ml-kem-1024"): return margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_1024;
+ case hashString("slh-dsa-sha2-128s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_128S;
+ case hashString("slh-dsa-sha2-128f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_128F;
+ case hashString("slh-dsa-sha2-192s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_192S;
+ case hashString("slh-dsa-sha2-192f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_192F;
+ case hashString("slh-dsa-sha2-256s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_256S;
+ case hashString("slh-dsa-sha2-256f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_256F;
+ case hashString("slh-dsa-shake-128s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_128S;
+ case hashString("slh-dsa-shake-128f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_128F;
+ case hashString("slh-dsa-shake-192s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_192S;
+ case hashString("slh-dsa-shake-192f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_192F;
+ case hashString("slh-dsa-shake-256s"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_256S;
+ case hashString("slh-dsa-shake-256f"): return margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_256F;
default: [[unlikely]]
throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum AsymmetricKeyType - invalid value!");
}
@@ -92,6 +116,18 @@ namespace margelo::nitro {
case margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_512: return JSIConverter::toJSI(runtime, "ml-kem-512");
case margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_768: return JSIConverter::toJSI(runtime, "ml-kem-768");
case margelo::nitro::crypto::AsymmetricKeyType::ML_KEM_1024: return JSIConverter::toJSI(runtime, "ml-kem-1024");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_128S: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-128s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_128F: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-128f");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_192S: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-192s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_192F: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-192f");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_256S: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-256s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHA2_256F: return JSIConverter::toJSI(runtime, "slh-dsa-sha2-256f");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_128S: return JSIConverter::toJSI(runtime, "slh-dsa-shake-128s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_128F: return JSIConverter::toJSI(runtime, "slh-dsa-shake-128f");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_192S: return JSIConverter::toJSI(runtime, "slh-dsa-shake-192s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_192F: return JSIConverter::toJSI(runtime, "slh-dsa-shake-192f");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_256S: return JSIConverter::toJSI(runtime, "slh-dsa-shake-256s");
+ case margelo::nitro::crypto::AsymmetricKeyType::SLH_DSA_SHAKE_256F: return JSIConverter::toJSI(runtime, "slh-dsa-shake-256f");
default: [[unlikely]]
throw std::invalid_argument("Cannot convert AsymmetricKeyType to JS - invalid value: "
+ std::to_string(static_cast(arg)) + "!");
@@ -118,6 +154,18 @@ namespace margelo::nitro {
case hashString("ml-kem-512"):
case hashString("ml-kem-768"):
case hashString("ml-kem-1024"):
+ case hashString("slh-dsa-sha2-128s"):
+ case hashString("slh-dsa-sha2-128f"):
+ case hashString("slh-dsa-sha2-192s"):
+ case hashString("slh-dsa-sha2-192f"):
+ case hashString("slh-dsa-sha2-256s"):
+ case hashString("slh-dsa-sha2-256f"):
+ case hashString("slh-dsa-shake-128s"):
+ case hashString("slh-dsa-shake-128f"):
+ case hashString("slh-dsa-shake-192s"):
+ case hashString("slh-dsa-shake-192f"):
+ case hashString("slh-dsa-shake-256s"):
+ case hashString("slh-dsa-shake-256f"):
return true;
default:
return false;
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.cpp
new file mode 100644
index 00000000..79d95548
--- /dev/null
+++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.cpp
@@ -0,0 +1,29 @@
+///
+/// HybridSlhDsaKeyPairSpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "HybridSlhDsaKeyPairSpec.hpp"
+
+namespace margelo::nitro::crypto {
+
+ void HybridSlhDsaKeyPairSpec::loadHybridMethods() {
+ // load base methods/properties
+ HybridObject::loadHybridMethods();
+ // load custom methods/properties
+ registerHybrids(this, [](Prototype& prototype) {
+ prototype.registerHybridMethod("generateKeyPair", &HybridSlhDsaKeyPairSpec::generateKeyPair);
+ prototype.registerHybridMethod("generateKeyPairSync", &HybridSlhDsaKeyPairSpec::generateKeyPairSync);
+ prototype.registerHybridMethod("getPublicKey", &HybridSlhDsaKeyPairSpec::getPublicKey);
+ prototype.registerHybridMethod("getPrivateKey", &HybridSlhDsaKeyPairSpec::getPrivateKey);
+ prototype.registerHybridMethod("sign", &HybridSlhDsaKeyPairSpec::sign);
+ prototype.registerHybridMethod("signSync", &HybridSlhDsaKeyPairSpec::signSync);
+ prototype.registerHybridMethod("verify", &HybridSlhDsaKeyPairSpec::verify);
+ prototype.registerHybridMethod("verifySync", &HybridSlhDsaKeyPairSpec::verifySync);
+ prototype.registerHybridMethod("setVariant", &HybridSlhDsaKeyPairSpec::setVariant);
+ });
+ }
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.hpp
new file mode 100644
index 00000000..43725476
--- /dev/null
+++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.hpp
@@ -0,0 +1,72 @@
+///
+/// HybridSlhDsaKeyPairSpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include()
+#include
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+
+
+#include
+#include
+#include
+
+namespace margelo::nitro::crypto {
+
+ using namespace margelo::nitro;
+
+ /**
+ * An abstract base class for `SlhDsaKeyPair`
+ * Inherit this class to create instances of `HybridSlhDsaKeyPairSpec` in C++.
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
+ * @example
+ * ```cpp
+ * class HybridSlhDsaKeyPair: public HybridSlhDsaKeyPairSpec {
+ * public:
+ * HybridSlhDsaKeyPair(...): HybridObject(TAG) { ... }
+ * // ...
+ * };
+ * ```
+ */
+ class HybridSlhDsaKeyPairSpec: public virtual HybridObject {
+ public:
+ // Constructor
+ explicit HybridSlhDsaKeyPairSpec(): HybridObject(TAG) { }
+
+ // Destructor
+ ~HybridSlhDsaKeyPairSpec() override = default;
+
+ public:
+ // Properties
+
+
+ public:
+ // Methods
+ virtual std::shared_ptr> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType) = 0;
+ virtual void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) = 0;
+ virtual std::shared_ptr getPublicKey() = 0;
+ virtual std::shared_ptr getPrivateKey() = 0;
+ virtual std::shared_ptr>> sign(const std::shared_ptr& message) = 0;
+ virtual std::shared_ptr signSync(const std::shared_ptr& message) = 0;
+ virtual std::shared_ptr> verify(const std::shared_ptr& signature, const std::shared_ptr& message) = 0;
+ virtual bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) = 0;
+ virtual void setVariant(const std::string& variant) = 0;
+
+ protected:
+ // Hybrid Setup
+ void loadHybridMethods() override;
+
+ protected:
+ // Tag for logging
+ static constexpr auto TAG = "SlhDsaKeyPair";
+ };
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts
index 0d2fefb7..3c0320bb 100644
--- a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts
+++ b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts
@@ -1,4 +1,5 @@
import { ed_generateKeyPair } from '../ed';
+import { Buffer } from '@craftzdog/react-native-buffer';
import { rsa_generateKeyPairNode, rsa_generateKeyPairNodeSync } from '../rsa';
import { ec_generateKeyPairNode, ec_generateKeyPairNodeSync } from '../ec';
import { dsa_generateKeyPairNode, dsa_generateKeyPairNodeSync } from '../dsa';
@@ -6,6 +7,7 @@ import {
dh_generateKeyPairNode,
dh_generateKeyPairNodeSync,
} from '../dhKeyPair';
+import { SlhDsa, type SlhDsaVariant } from '../slhdsa';
import {
kEmptyObject,
validateFunction,
@@ -17,9 +19,123 @@ import {
type KeyPairGenConfig,
type KeyPairKey,
type KeyPairType,
+ type SlhDsaKeyPairType,
+ KFormatType,
+ KeyEncoding,
} from '../utils';
+import {
+ KeyObject,
+ type PublicKeyObject,
+ type PrivateKeyObject,
+} from './classes';
import { parsePrivateKeyEncoding, parsePublicKeyEncoding } from './utils';
+const SLH_DSA_TYPE_TO_VARIANT: Readonly<
+ Record
+> = {
+ 'slh-dsa-sha2-128s': 'SLH-DSA-SHA2-128s',
+ 'slh-dsa-sha2-128f': 'SLH-DSA-SHA2-128f',
+ 'slh-dsa-sha2-192s': 'SLH-DSA-SHA2-192s',
+ 'slh-dsa-sha2-192f': 'SLH-DSA-SHA2-192f',
+ 'slh-dsa-sha2-256s': 'SLH-DSA-SHA2-256s',
+ 'slh-dsa-sha2-256f': 'SLH-DSA-SHA2-256f',
+ 'slh-dsa-shake-128s': 'SLH-DSA-SHAKE-128s',
+ 'slh-dsa-shake-128f': 'SLH-DSA-SHAKE-128f',
+ 'slh-dsa-shake-192s': 'SLH-DSA-SHAKE-192s',
+ 'slh-dsa-shake-192f': 'SLH-DSA-SHAKE-192f',
+ 'slh-dsa-shake-256s': 'SLH-DSA-SHAKE-256s',
+ 'slh-dsa-shake-256f': 'SLH-DSA-SHAKE-256f',
+};
+
+function isSlhDsaType(type: string): type is SlhDsaKeyPairType {
+ return type in SLH_DSA_TYPE_TO_VARIANT;
+}
+
+function slhDsaFormatKeyPairOutput(
+ slhdsa: SlhDsa,
+ encoding: KeyPairGenConfig,
+): {
+ publicKey: PublicKeyObject | string | ArrayBuffer;
+ privateKey: PrivateKeyObject | string | ArrayBuffer;
+} {
+ const { publicFormat, privateFormat, cipher, passphrase } = encoding;
+
+ const publicKey = KeyObject.createKeyObject(
+ 'public',
+ slhdsa.getPublicKey(),
+ KFormatType.DER,
+ KeyEncoding.SPKI,
+ ) as PublicKeyObject;
+ const privateKey = KeyObject.createKeyObject(
+ 'private',
+ slhdsa.getPrivateKey(),
+ KFormatType.DER,
+ KeyEncoding.PKCS8,
+ ) as PrivateKeyObject;
+
+ let publicKeyOutput: PublicKeyObject | string | ArrayBuffer;
+ let privateKeyOutput: PrivateKeyObject | string | ArrayBuffer;
+
+ if (publicFormat === -1) {
+ publicKeyOutput = publicKey;
+ } else {
+ const format =
+ publicFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER;
+ const exported = publicKey.handle.exportKey(format, KeyEncoding.SPKI);
+ if (format === KFormatType.PEM) {
+ publicKeyOutput = Buffer.from(new Uint8Array(exported)).toString('utf-8');
+ } else {
+ publicKeyOutput = exported;
+ }
+ }
+
+ if (privateFormat === -1) {
+ privateKeyOutput = privateKey;
+ } else {
+ const format =
+ privateFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER;
+ const exported = privateKey.handle.exportKey(
+ format,
+ KeyEncoding.PKCS8,
+ cipher,
+ passphrase,
+ );
+ if (format === KFormatType.PEM) {
+ privateKeyOutput = Buffer.from(new Uint8Array(exported)).toString(
+ 'utf-8',
+ );
+ } else {
+ privateKeyOutput = exported;
+ }
+ }
+
+ return { publicKey: publicKeyOutput, privateKey: privateKeyOutput };
+}
+
+function slhDsaGenerateKeyPairNodeSync(
+ type: SlhDsaKeyPairType,
+ encoding: KeyPairGenConfig,
+): {
+ publicKey: PublicKeyObject | string | ArrayBuffer;
+ privateKey: PrivateKeyObject | string | ArrayBuffer;
+} {
+ const slhdsa = new SlhDsa(SLH_DSA_TYPE_TO_VARIANT[type]);
+ slhdsa.generateKeyPairSync();
+ return slhDsaFormatKeyPairOutput(slhdsa, encoding);
+}
+
+async function slhDsaGenerateKeyPairNode(
+ type: SlhDsaKeyPairType,
+ encoding: KeyPairGenConfig,
+): Promise<{
+ publicKey: PublicKeyObject | string | ArrayBuffer;
+ privateKey: PrivateKeyObject | string | ArrayBuffer;
+}> {
+ const slhdsa = new SlhDsa(SLH_DSA_TYPE_TO_VARIANT[type]);
+ await slhdsa.generateKeyPair();
+ return slhDsaFormatKeyPairOutput(slhdsa, encoding);
+}
+
export const generateKeyPair = (
type: KeyPairType,
options: GenerateKeyPairOptions,
@@ -147,6 +263,9 @@ function internalGenerateKeyPair(
case 'dh':
break;
default: {
+ if (isSlhDsaType(type)) {
+ break;
+ }
const err = new Error(`
Invalid Argument options: '${type}' scheme not supported for
generateKeyPair(). Currently not all encryption methods are supported in
@@ -168,6 +287,8 @@ function internalGenerateKeyPair(
result = await dsa_generateKeyPairNode(options, encoding);
} else if (type === 'dh') {
result = await dh_generateKeyPairNode(options, encoding);
+ } else if (isSlhDsaType(type)) {
+ result = await slhDsaGenerateKeyPairNode(type, encoding);
} else {
throw new Error(`Unsupported key type: ${type}`);
}
@@ -198,6 +319,8 @@ function internalGenerateKeyPair(
result = dsa_generateKeyPairNodeSync(options, encoding);
} else if (type === 'dh') {
result = dh_generateKeyPairNodeSync(options, encoding);
+ } else if (isSlhDsaType(type)) {
+ result = slhDsaGenerateKeyPairNodeSync(type, encoding);
} else {
throw new Error(`Unsupported key type: ${type}`);
}
diff --git a/packages/react-native-quick-crypto/src/slhdsa.ts b/packages/react-native-quick-crypto/src/slhdsa.ts
new file mode 100644
index 00000000..4af1f75d
--- /dev/null
+++ b/packages/react-native-quick-crypto/src/slhdsa.ts
@@ -0,0 +1,146 @@
+import { NitroModules } from 'react-native-nitro-modules';
+import type { SlhDsaKeyPair } from './specs/slhDsaKeyPair.nitro';
+import {
+ CryptoKey,
+ KeyObject,
+ PublicKeyObject,
+ PrivateKeyObject as PrivateKeyObjectClass,
+} from './keys/classes';
+import type {
+ CryptoKeyPair,
+ KeyUsage,
+ SubtleAlgorithm,
+ SlhDsaAlgorithm,
+} from './utils';
+import {
+ hasAnyNotIn,
+ lazyDOMException,
+ getUsagesUnion,
+ KFormatType,
+ KeyEncoding,
+} from './utils';
+
+export type SlhDsaVariant = SlhDsaAlgorithm;
+
+export const SLH_DSA_VARIANTS: readonly SlhDsaVariant[] = [
+ 'SLH-DSA-SHA2-128s',
+ 'SLH-DSA-SHA2-128f',
+ 'SLH-DSA-SHA2-192s',
+ 'SLH-DSA-SHA2-192f',
+ 'SLH-DSA-SHA2-256s',
+ 'SLH-DSA-SHA2-256f',
+ 'SLH-DSA-SHAKE-128s',
+ 'SLH-DSA-SHAKE-128f',
+ 'SLH-DSA-SHAKE-192s',
+ 'SLH-DSA-SHAKE-192f',
+ 'SLH-DSA-SHAKE-256s',
+ 'SLH-DSA-SHAKE-256f',
+] as const;
+
+export class SlhDsa {
+ variant: SlhDsaVariant;
+ native: SlhDsaKeyPair;
+
+ constructor(variant: SlhDsaVariant) {
+ this.variant = variant;
+ this.native =
+ NitroModules.createHybridObject('SlhDsaKeyPair');
+ this.native.setVariant(variant);
+ }
+
+ async generateKeyPair(): Promise {
+ await this.native.generateKeyPair(
+ KFormatType.DER,
+ KeyEncoding.SPKI,
+ KFormatType.DER,
+ KeyEncoding.PKCS8,
+ );
+ }
+
+ generateKeyPairSync(): void {
+ this.native.generateKeyPairSync(
+ KFormatType.DER,
+ KeyEncoding.SPKI,
+ KFormatType.DER,
+ KeyEncoding.PKCS8,
+ );
+ }
+
+ getPublicKey(): ArrayBuffer {
+ return this.native.getPublicKey();
+ }
+
+ getPrivateKey(): ArrayBuffer {
+ return this.native.getPrivateKey();
+ }
+
+ async sign(message: ArrayBuffer): Promise {
+ return this.native.sign(message);
+ }
+
+ signSync(message: ArrayBuffer): ArrayBuffer {
+ return this.native.signSync(message);
+ }
+
+ async verify(signature: ArrayBuffer, message: ArrayBuffer): Promise {
+ return this.native.verify(signature, message);
+ }
+
+ verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean {
+ return this.native.verifySync(signature, message);
+ }
+}
+
+export async function slhdsa_generateKeyPairWebCrypto(
+ variant: SlhDsaVariant,
+ extractable: boolean,
+ keyUsages: KeyUsage[],
+): Promise {
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
+ throw lazyDOMException(
+ `Unsupported key usage for ${variant}`,
+ 'SyntaxError',
+ );
+ }
+
+ const publicUsages = getUsagesUnion(keyUsages, 'verify');
+ const privateUsages = getUsagesUnion(keyUsages, 'sign');
+
+ if (privateUsages.length === 0) {
+ throw lazyDOMException('Usages cannot be empty', 'SyntaxError');
+ }
+
+ const slhdsa = new SlhDsa(variant);
+ await slhdsa.generateKeyPair();
+
+ const publicKeyData = slhdsa.getPublicKey();
+ const privateKeyData = slhdsa.getPrivateKey();
+
+ const pub = KeyObject.createKeyObject(
+ 'public',
+ publicKeyData,
+ KFormatType.DER,
+ KeyEncoding.SPKI,
+ ) as PublicKeyObject;
+ const publicKey = new CryptoKey(
+ pub,
+ { name: variant } as SubtleAlgorithm,
+ publicUsages,
+ true,
+ );
+
+ const priv = KeyObject.createKeyObject(
+ 'private',
+ privateKeyData,
+ KFormatType.DER,
+ KeyEncoding.PKCS8,
+ ) as PrivateKeyObjectClass;
+ const privateKey = new CryptoKey(
+ priv,
+ { name: variant } as SubtleAlgorithm,
+ privateUsages,
+ extractable,
+ );
+
+ return { publicKey, privateKey };
+}
diff --git a/packages/react-native-quick-crypto/src/specs/slhDsaKeyPair.nitro.ts b/packages/react-native-quick-crypto/src/specs/slhDsaKeyPair.nitro.ts
new file mode 100644
index 00000000..d954be05
--- /dev/null
+++ b/packages/react-native-quick-crypto/src/specs/slhDsaKeyPair.nitro.ts
@@ -0,0 +1,29 @@
+import type { HybridObject } from 'react-native-nitro-modules';
+
+export interface SlhDsaKeyPair
+ extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
+ generateKeyPair(
+ publicFormat: number,
+ publicType: number,
+ privateFormat: number,
+ privateType: number,
+ ): Promise;
+
+ generateKeyPairSync(
+ publicFormat: number,
+ publicType: number,
+ privateFormat: number,
+ privateType: number,
+ ): void;
+
+ getPublicKey(): ArrayBuffer;
+ getPrivateKey(): ArrayBuffer;
+
+ sign(message: ArrayBuffer): Promise;
+ signSync(message: ArrayBuffer): ArrayBuffer;
+
+ verify(signature: ArrayBuffer, message: ArrayBuffer): Promise;
+ verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean;
+
+ setVariant(variant: string): void;
+}
diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts
index 83d4bd64..39d36a3c 100644
--- a/packages/react-native-quick-crypto/src/subtle.ts
+++ b/packages/react-native-quick-crypto/src/subtle.ts
@@ -62,6 +62,11 @@ import {
Ed,
} from './ed';
import { mldsa_generateKeyPairWebCrypto, type MlDsaVariant } from './mldsa';
+import {
+ slhdsa_generateKeyPairWebCrypto,
+ SLH_DSA_VARIANTS,
+ type SlhDsaVariant,
+} from './slhdsa';
import {
mlkem_generateKeyPairWebCrypto,
type MlKemVariant,
@@ -1435,13 +1440,25 @@ export const PQC_SEEDLESS_PKCS8_LENGTHS: Readonly> = {
// Map from PQC algorithm name to display family. Used to render the
// import-rejection error message in the same form Node emits.
-const PQC_FAMILY: Readonly> = {
+const PQC_FAMILY: Readonly> = {
'ML-DSA-44': 'ML-DSA',
'ML-DSA-65': 'ML-DSA',
'ML-DSA-87': 'ML-DSA',
'ML-KEM-512': 'ML-KEM',
'ML-KEM-768': 'ML-KEM',
'ML-KEM-1024': 'ML-KEM',
+ 'SLH-DSA-SHA2-128s': 'SLH-DSA',
+ 'SLH-DSA-SHA2-128f': 'SLH-DSA',
+ 'SLH-DSA-SHA2-192s': 'SLH-DSA',
+ 'SLH-DSA-SHA2-192f': 'SLH-DSA',
+ 'SLH-DSA-SHA2-256s': 'SLH-DSA',
+ 'SLH-DSA-SHA2-256f': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-128s': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-128f': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-192s': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-192f': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-256s': 'SLH-DSA',
+ 'SLH-DSA-SHAKE-256f': 'SLH-DSA',
};
function pqcImportKeyObject(
@@ -1620,6 +1637,16 @@ function mldsaImportKey(
return new CryptoKey(keyObject, { name }, keyUsages, extractable);
}
+function slhdsaImportKey(
+ format: ImportFormat,
+ data: BufferLike | JWK,
+ algorithm: SubtleAlgorithm,
+ extractable: boolean,
+ keyUsages: KeyUsage[],
+): CryptoKey {
+ return mldsaImportKey(format, data, algorithm, extractable, keyUsages);
+}
+
function mlkemImportKey(
format: ImportFormat,
data: BufferLike | JWK,
@@ -1685,8 +1712,21 @@ const exportKeySpki = async (
case 'ML-DSA-65':
// Fall through
case 'ML-DSA-87':
+ // Fall through
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
if (key.type === 'public') {
- // Export ML-DSA key in SPKI DER format
+ // Export ML-DSA / SLH-DSA key in SPKI DER format
return bufferLikeToArrayBuffer(
key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI),
);
@@ -1771,6 +1811,24 @@ const exportKeyPkcs8 = async (
return ab;
}
break;
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
+ if (key.type === 'private') {
+ return bufferLikeToArrayBuffer(
+ key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8),
+ );
+ }
+ break;
}
throw new Error(
@@ -1847,8 +1905,20 @@ const exportKeyRaw = (
case 'ML-KEM-512':
case 'ML-KEM-768':
case 'ML-KEM-1024':
- // ML-DSA / ML-KEM keys do not recognize plain 'raw' (Node webcrypto.js
- // lines 488-510).
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
+ // ML-DSA / ML-KEM / SLH-DSA keys do not recognize plain 'raw' (Node
+ // webcrypto.js lines 488-510).
if (!isPublic) return fail();
if (format === 'raw-public') return exportRawPublic();
return fail();
@@ -1908,6 +1978,19 @@ const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => {
case 'ML-KEM-768':
// Fall through
case 'ML-KEM-1024':
+ // Fall through
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
return jwk;
case 'AES-CTR':
// Fall through
@@ -2289,6 +2372,18 @@ const signVerify = (
case 'ML-DSA-44':
case 'ML-DSA-65':
case 'ML-DSA-87':
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
return mldsaSignVerify(key, data, signature);
case 'KMAC128':
case 'KMAC256':
@@ -2364,6 +2459,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
'ML-DSA-44',
'ML-DSA-65',
'ML-DSA-87',
+ ...SLH_DSA_VARIANTS,
]),
verify: new Set([
'RSASSA-PKCS1-v1_5',
@@ -2377,6 +2473,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
'ML-DSA-44',
'ML-DSA-65',
'ML-DSA-87',
+ ...SLH_DSA_VARIANTS,
]),
digest: new Set([
'SHA-1',
@@ -2414,6 +2511,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
'ML-KEM-512',
'ML-KEM-768',
'ML-KEM-1024',
+ ...SLH_DSA_VARIANTS,
]),
importKey: new Set([
'RSASSA-PKCS1-v1_5',
@@ -2445,6 +2543,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
'ML-KEM-512',
'ML-KEM-768',
'ML-KEM-1024',
+ ...SLH_DSA_VARIANTS,
]),
exportKey: new Set([
'RSASSA-PKCS1-v1_5',
@@ -2471,6 +2570,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
'ML-KEM-512',
'ML-KEM-768',
'ML-KEM-1024',
+ ...SLH_DSA_VARIANTS,
]),
deriveBits: new Set([
'PBKDF2',
@@ -2506,7 +2606,7 @@ const SUPPORTED_ALGORITHMS: Record> = {
decapsulateKey: new Set(['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
};
-const ASYMMETRIC_ALGORITHMS = new Set([
+const ASYMMETRIC_ALGORITHMS = new Set([
'RSASSA-PKCS1-v1_5',
'RSA-PSS',
'RSA-OAEP',
@@ -2522,6 +2622,7 @@ const ASYMMETRIC_ALGORITHMS = new Set([
'ML-KEM-512',
'ML-KEM-768',
'ML-KEM-1024',
+ ...SLH_DSA_VARIANTS,
]);
// Per-algorithm validators for deriveBits (mirrors Node's hkdf.js:141-149,
@@ -2964,13 +3065,14 @@ export class Subtle {
throw lazyDOMException('key is not extractable', 'InvalidAccessError');
if (format === 'raw-seed') {
- const pqcAlgos = [
+ const pqcAlgos: string[] = [
'ML-KEM-512',
'ML-KEM-768',
'ML-KEM-1024',
'ML-DSA-44',
'ML-DSA-65',
'ML-DSA-87',
+ ...SLH_DSA_VARIANTS,
];
if (!pqcAlgos.includes(key.algorithm.name)) {
throw lazyDOMException(
@@ -3216,6 +3318,25 @@ export class Subtle {
);
checkCryptoKeyPairUsages(result as CryptoKeyPair);
break;
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
+ result = await slhdsa_generateKeyPairWebCrypto(
+ algorithm.name as SlhDsaVariant,
+ extractable,
+ keyUsages,
+ );
+ checkCryptoKeyPairUsages(result as CryptoKeyPair);
+ break;
case 'X25519':
// Fall through
case 'X448':
@@ -3389,6 +3510,26 @@ export class Subtle {
keyUsages,
);
break;
+ case 'SLH-DSA-SHA2-128s':
+ case 'SLH-DSA-SHA2-128f':
+ case 'SLH-DSA-SHA2-192s':
+ case 'SLH-DSA-SHA2-192f':
+ case 'SLH-DSA-SHA2-256s':
+ case 'SLH-DSA-SHA2-256f':
+ case 'SLH-DSA-SHAKE-128s':
+ case 'SLH-DSA-SHAKE-128f':
+ case 'SLH-DSA-SHAKE-192s':
+ case 'SLH-DSA-SHAKE-192f':
+ case 'SLH-DSA-SHAKE-256s':
+ case 'SLH-DSA-SHAKE-256f':
+ result = slhdsaImportKey(
+ format,
+ data as BufferLike | JWK,
+ normalizedAlgorithm,
+ extractable,
+ keyUsages,
+ );
+ break;
case 'ML-DSA-44':
// Fall through
case 'ML-DSA-65':
diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts
index 83562b94..f739a08c 100644
--- a/packages/react-native-quick-crypto/src/utils/types.ts
+++ b/packages/react-native-quick-crypto/src/utils/types.ts
@@ -74,20 +74,50 @@ export type ECKeyPairAlgorithm = 'ECDSA' | 'ECDH';
export type CFRGKeyPairAlgorithm = 'Ed25519' | 'Ed448' | 'X25519' | 'X448';
export type CFRGKeyPairType = 'ed25519' | 'ed448' | 'x25519' | 'x448';
+export type SlhDsaAlgorithm =
+ | 'SLH-DSA-SHA2-128s'
+ | 'SLH-DSA-SHA2-128f'
+ | 'SLH-DSA-SHA2-192s'
+ | 'SLH-DSA-SHA2-192f'
+ | 'SLH-DSA-SHA2-256s'
+ | 'SLH-DSA-SHA2-256f'
+ | 'SLH-DSA-SHAKE-128s'
+ | 'SLH-DSA-SHAKE-128f'
+ | 'SLH-DSA-SHAKE-192s'
+ | 'SLH-DSA-SHAKE-192f'
+ | 'SLH-DSA-SHAKE-256s'
+ | 'SLH-DSA-SHAKE-256f';
+
+export type SlhDsaKeyPairType =
+ | 'slh-dsa-sha2-128s'
+ | 'slh-dsa-sha2-128f'
+ | 'slh-dsa-sha2-192s'
+ | 'slh-dsa-sha2-192f'
+ | 'slh-dsa-sha2-256s'
+ | 'slh-dsa-sha2-256f'
+ | 'slh-dsa-shake-128s'
+ | 'slh-dsa-shake-128f'
+ | 'slh-dsa-shake-192s'
+ | 'slh-dsa-shake-192f'
+ | 'slh-dsa-shake-256s'
+ | 'slh-dsa-shake-256f';
+
export type PQCKeyPairAlgorithm =
| 'ML-DSA-44'
| 'ML-DSA-65'
| 'ML-DSA-87'
| 'ML-KEM-512'
| 'ML-KEM-768'
- | 'ML-KEM-1024';
+ | 'ML-KEM-1024'
+ | SlhDsaAlgorithm;
export type PQCKeyPairType =
| 'ml-dsa-44'
| 'ml-dsa-65'
| 'ml-dsa-87'
| 'ml-kem-512'
| 'ml-kem-768'
- | 'ml-kem-1024';
+ | 'ml-kem-1024'
+ | SlhDsaKeyPairType;
export type MlKemAlgorithm = 'ML-KEM-512' | 'ML-KEM-768' | 'ML-KEM-1024';
@@ -128,7 +158,8 @@ export type SignVerifyAlgorithm =
| 'Ed448'
| 'ML-DSA-44'
| 'ML-DSA-65'
- | 'ML-DSA-87';
+ | 'ML-DSA-87'
+ | SlhDsaAlgorithm;
export type Argon2Algorithm = 'Argon2d' | 'Argon2i' | 'Argon2id';
@@ -255,7 +286,8 @@ export type KeyPairType =
| RSAKeyPairType
| ECKeyPairType
| DSAKeyPairType
- | DHKeyPairType;
+ | DHKeyPairType
+ | SlhDsaKeyPairType;
export type KeyUsage =
| 'encrypt'