pq_crypto 0.3.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +56 -0
- data/CHANGELOG.md +50 -0
- data/GET_STARTED.md +374 -30
- data/README.md +59 -195
- data/SECURITY.md +101 -82
- data/ext/pqcrypto/extconf.rb +85 -9
- data/ext/pqcrypto/mldsa_api.h +71 -1
- data/ext/pqcrypto/mlkem_api.h +24 -0
- data/ext/pqcrypto/pq_externalmu.c +310 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +784 -85
- data/ext/pqcrypto/pqcrypto_secure.c +179 -72
- data/ext/pqcrypto/pqcrypto_secure.h +103 -7
- data/ext/pqcrypto/pqcrypto_version.h +7 -0
- data/ext/pqcrypto/vendor/.vendored +1 -1
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +8 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/api.h +18 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/cbd.c +83 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/cbd.h +11 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/indcpa.c +327 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/indcpa.h +22 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/kem.c +164 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/kem.h +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/ntt.c +146 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/ntt.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/params.h +36 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/poly.c +311 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/poly.h +37 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/polyvec.c +198 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/polyvec.h +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/reduce.c +41 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/reduce.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/symmetric-shake.c +71 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/symmetric.h +30 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/verify.c +67 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/verify.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/api.h +18 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/cbd.c +108 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/cbd.h +11 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/indcpa.c +327 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/indcpa.h +22 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/kem.c +164 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/kem.h +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/ntt.c +146 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/ntt.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/params.h +36 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/poly.c +299 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/poly.h +37 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/polyvec.c +188 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/polyvec.h +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/reduce.c +41 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/reduce.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/symmetric-shake.c +71 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/symmetric.h +30 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/verify.c +67 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/verify.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/api.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/ntt.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/ntt.h +10 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/packing.c +261 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/packing.h +31 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/params.h +44 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/poly.c +848 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/poly.h +52 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/polyvec.c +415 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/polyvec.h +65 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/reduce.c +69 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/reduce.h +17 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/rounding.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/rounding.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/sign.c +407 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/sign.h +47 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/symmetric-shake.c +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/symmetric.h +34 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/api.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/ntt.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/ntt.h +10 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/packing.c +261 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/packing.h +31 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/params.h +44 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/poly.c +823 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/poly.h +52 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/polyvec.c +415 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/polyvec.h +65 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/reduce.c +69 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/reduce.h +17 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/rounding.c +92 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/rounding.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/sign.c +407 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/sign.h +47 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/symmetric-shake.c +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/symmetric.h +34 -0
- data/lib/pq_crypto/algorithm_registry.rb +200 -0
- data/lib/pq_crypto/hybrid_kem.rb +1 -12
- data/lib/pq_crypto/kem.rb +104 -13
- data/lib/pq_crypto/pkcs8.rb +387 -0
- data/lib/pq_crypto/serialization.rb +1 -14
- data/lib/pq_crypto/signature.rb +231 -13
- data/lib/pq_crypto/spki.rb +131 -0
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +90 -19
- data/script/vendor_libs.rb +4 -0
- metadata +99 -3
data/README.md
CHANGED
|
@@ -1,237 +1,101 @@
|
|
|
1
1
|
# pq_crypto
|
|
2
2
|
|
|
3
3
|
`pq_crypto` is a primitive-first Ruby gem for post-quantum cryptography.
|
|
4
|
+
It provides small Ruby APIs for ML-KEM, ML-DSA, and one hybrid X-Wing KEM,
|
|
5
|
+
with standard key serialization where available.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- `PQCrypto::Signature` — `ML-DSA-65` (FIPS 204)
|
|
9
|
-
- `PQCrypto::HybridKEM` — `ML-KEM-768 + X25519` combined via the
|
|
10
|
-
[X-Wing](https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/)
|
|
11
|
-
SHA3-256 combiner
|
|
12
|
-
|
|
13
|
-
The gem is backed by vendored `PQClean` sources for `ML-KEM-768` /
|
|
14
|
-
`ML-DSA-65` and by OpenSSL for `X25519` and `SHA3-256`. Every piece of
|
|
15
|
-
conventional-crypto functionality goes through standard library calls
|
|
16
|
-
(`EVP_*`, `RAND_bytes`, `CRYPTO_memcmp`, `BIO_f_base64`) — nothing
|
|
17
|
-
roll-your-own where a library primitive exists.
|
|
18
|
-
|
|
19
|
-
## Status
|
|
20
|
-
|
|
21
|
-
- primitive-first API only
|
|
22
|
-
- no protocol/session helpers in the public surface
|
|
23
|
-
- serialization uses pq_crypto-specific `pqc_container_*` wrappers
|
|
24
|
-
- not audited
|
|
25
|
-
- not yet positioned as production-ready
|
|
7
|
+
The gem intentionally stays close to cryptographic primitives. It does not
|
|
8
|
+
provide protocol, session, transport, certificate-chain, or application-level
|
|
9
|
+
handshake helpers.
|
|
26
10
|
|
|
27
11
|
## Installation
|
|
28
12
|
|
|
29
|
-
Add the gem to your
|
|
13
|
+
Add the gem to your `Gemfile`:
|
|
30
14
|
|
|
31
15
|
```ruby
|
|
32
16
|
# Gemfile
|
|
33
17
|
gem "pq_crypto"
|
|
34
18
|
```
|
|
35
19
|
|
|
20
|
+
Then install it:
|
|
21
|
+
|
|
36
22
|
```bash
|
|
37
23
|
bundle install
|
|
38
|
-
bundle exec rake compile
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Native dependencies
|
|
42
|
-
|
|
43
|
-
- Ruby 3.4.x
|
|
44
|
-
- a C toolchain with C11 support (for `_Static_assert` / `_Thread_local`)
|
|
45
|
-
- OpenSSL **3.0 or later** with SHA3-256 and SHAKE256 available (default provider)
|
|
46
|
-
|
|
47
|
-
## Async / Fiber scheduler support
|
|
48
|
-
|
|
49
|
-
`pq_crypto` does not require any gem-specific Async configuration. On
|
|
50
|
-
Ruby 3.4, `sign` and `verify` use Ruby's scheduler-aware
|
|
51
|
-
`rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` path automatically.
|
|
52
|
-
|
|
53
|
-
That means:
|
|
54
|
-
|
|
55
|
-
- without a Fiber scheduler, these methods fall back to the ordinary
|
|
56
|
-
no-GVL behavior;
|
|
57
|
-
- with a scheduler that implements `blocking_operation_wait` (for
|
|
58
|
-
example `Async` with a worker pool), the blocking native work can
|
|
59
|
-
be moved off the event loop.
|
|
60
|
-
|
|
61
|
-
This integration is intentionally limited to `sign` and `verify`; the
|
|
62
|
-
faster primitive operations keep the lower-overhead path.
|
|
63
|
-
|
|
64
|
-
Example with `Async`:
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
require "async"
|
|
68
|
-
require "pq_crypto"
|
|
69
|
-
|
|
70
|
-
keypair = PQCrypto::Signature.generate(:ml_dsa_65)
|
|
71
|
-
message = "hello" * 100_000
|
|
72
|
-
|
|
73
|
-
reactor = Async::Reactor.new(worker_pool: true)
|
|
74
|
-
root = reactor.async do |task|
|
|
75
|
-
task.async do
|
|
76
|
-
signature = keypair.secret_key.sign(message)
|
|
77
|
-
keypair.public_key.verify(message, signature)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
task.async do
|
|
81
|
-
sleep 0.01
|
|
82
|
-
puts "event loop stayed responsive"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
reactor.run
|
|
87
|
-
root.wait
|
|
88
|
-
reactor.close
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Primitive API
|
|
92
|
-
|
|
93
|
-
### ML-KEM-768
|
|
94
|
-
|
|
95
|
-
```ruby
|
|
96
|
-
keypair = PQCrypto::KEM.generate(:ml_kem_768)
|
|
97
|
-
result = keypair.public_key.encapsulate
|
|
98
|
-
shared_secret = keypair.secret_key.decapsulate(result.ciphertext)
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### ML-DSA-65
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
keypair = PQCrypto::Signature.generate(:ml_dsa_65)
|
|
105
|
-
signature = keypair.secret_key.sign("hello")
|
|
106
|
-
|
|
107
|
-
keypair.public_key.verify("hello", signature) # => true / false
|
|
108
|
-
keypair.public_key.verify!("hello", signature) # raises on mismatch
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Note: `verify` returns a plain boolean for normal outcomes. `verify!`
|
|
112
|
-
raises `PQCrypto::VerificationError` when the signature does not
|
|
113
|
-
match.
|
|
114
|
-
|
|
115
|
-
### Hybrid ML-KEM-768 + X25519 (X-Wing)
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
keypair = PQCrypto::HybridKEM.generate(:ml_kem_768_x25519_xwing)
|
|
119
|
-
result = keypair.public_key.encapsulate
|
|
120
|
-
shared_secret = keypair.secret_key.decapsulate(result.ciphertext)
|
|
121
24
|
```
|
|
122
25
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
and X25519 private material. The combiner is exactly:
|
|
26
|
+
When working from a source checkout, compile the native extension before
|
|
27
|
+
running tests or examples:
|
|
126
28
|
|
|
127
|
-
```
|
|
128
|
-
|
|
29
|
+
```bash
|
|
30
|
+
bundle exec rake compile
|
|
31
|
+
bundle exec rake test
|
|
129
32
|
```
|
|
130
33
|
|
|
131
|
-
|
|
132
|
-
for audit status and interoperability caveats.
|
|
34
|
+
## What this gem provides
|
|
133
35
|
|
|
134
|
-
|
|
36
|
+
| Area | Capabilities |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| ML-KEM | Key generation, encapsulation, decapsulation, raw key import/export, SPKI public keys, PKCS#8 private keys. |
|
|
39
|
+
| ML-DSA | Key generation, signing, verification, streaming signing/verification for large inputs, raw key import/export, SPKI public keys, PKCS#8 private keys. |
|
|
40
|
+
| Hybrid KEM | ML-KEM-768 + X25519 using the X-Wing combiner. |
|
|
41
|
+
| Serialization | Standard SPKI / PKCS#8 for NIST PQC keys, plus frozen `pqc_container_*` compatibility formats for the original algorithms. |
|
|
42
|
+
| Safety helpers | Best-effort secret wiping and constant-time equality for key comparisons. |
|
|
43
|
+
| Introspection | Supported algorithm lists, algorithm metadata, backend/version helpers. |
|
|
135
44
|
|
|
136
|
-
|
|
45
|
+
## Supported algorithms
|
|
137
46
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
47
|
+
| Family | Algorithms | Notes |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| KEM | `:ml_kem_512`, `:ml_kem_768`, `:ml_kem_1024` | FIPS 203 ML-KEM. Standard SPKI public keys and PKCS#8 private keys. |
|
|
50
|
+
| Signature | `:ml_dsa_44`, `:ml_dsa_65`, `:ml_dsa_87` | FIPS 204 ML-DSA. Standard SPKI public keys and PKCS#8 private keys. |
|
|
51
|
+
| Hybrid KEM | `:ml_kem_768_x25519_xwing` | ML-KEM-768 + X25519 hybrid KEM using the X-Wing construction. |
|
|
142
52
|
|
|
143
|
-
|
|
53
|
+
Standard encodings use RFC 9935 OIDs for ML-KEM and RFC 9881 OIDs for
|
|
54
|
+
ML-DSA. `AlgorithmIdentifier.parameters` are omitted, not encoded as `NULL`.
|
|
144
55
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
imported = PQCrypto::KEM.public_key_from_pqc_container_der(der)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
These containers are **not real ASN.1 SPKI or PKCS#8**. They are
|
|
152
|
-
intended for stable import/export inside `pq_crypto` itself and are
|
|
153
|
-
not advertised as interoperable with external PKI tooling.
|
|
56
|
+
The `pqc_container_*` format is project-local and kept only for backward
|
|
57
|
+
compatibility. It is not ASN.1, SPKI, or PKCS#8. It remains limited to the
|
|
58
|
+
original algorithms:
|
|
154
59
|
|
|
155
|
-
|
|
60
|
+
- `:ml_kem_768`
|
|
61
|
+
- `:ml_dsa_65`
|
|
62
|
+
- `:ml_kem_768_x25519_xwing`
|
|
156
63
|
|
|
157
|
-
|
|
158
|
-
in place. Key objects hold a private copy of their bytes, so `wipe!`
|
|
159
|
-
on a `SecretKey` zeroes **only** that internal copy — any prior Ruby
|
|
160
|
-
string the caller holds is untouched. If you need to wipe the
|
|
161
|
-
caller-side buffer, do so explicitly:
|
|
64
|
+
## Requirements
|
|
162
65
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
PQCrypto.secure_wipe(raw) # scrub the original input
|
|
167
|
-
# ... use key ...
|
|
168
|
-
key.wipe! # scrub the key's internal copy
|
|
169
|
-
```
|
|
66
|
+
- Ruby 3.4 or later
|
|
67
|
+
- a C toolchain with C11 support
|
|
68
|
+
- OpenSSL 3.0 or later with SHA3-256 and SHAKE256 available
|
|
170
69
|
|
|
171
|
-
##
|
|
70
|
+
## Security status
|
|
172
71
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
72
|
+
`pq_crypto` is experimental and not audited. Treat it as a low-level primitive
|
|
73
|
+
library, not a complete security protocol. See [`SECURITY.md`](SECURITY.md) for
|
|
74
|
+
audit status, serialization caveats, hybrid-KEM notes, and interoperability
|
|
75
|
+
warnings.
|
|
176
76
|
|
|
177
|
-
|
|
178
|
-
objects do not expose a public `fingerprint` method. `wipe!` remains
|
|
179
|
-
best-effort only: it clears the current Ruby string buffer owned by the
|
|
180
|
-
key object, not every possible copy made by Ruby, OpenSSL, serialization,
|
|
181
|
-
logging, or the garbage collector.
|
|
182
|
-
|
|
183
|
-
## Introspection
|
|
77
|
+
## Useful entry points
|
|
184
78
|
|
|
185
79
|
```ruby
|
|
186
80
|
PQCrypto.version
|
|
187
81
|
PQCrypto.backend
|
|
188
82
|
PQCrypto.supported_kems
|
|
189
|
-
PQCrypto.supported_hybrid_kems
|
|
190
83
|
PQCrypto.supported_signatures
|
|
191
|
-
PQCrypto
|
|
192
|
-
PQCrypto::HybridKEM.details(:ml_kem_768_x25519_xwing)
|
|
193
|
-
PQCrypto::Signature.details(:ml_dsa_65)
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Testing helpers
|
|
197
|
-
|
|
198
|
-
Deterministic test hooks are exposed under `PQCrypto::Testing` for
|
|
199
|
-
regression coverage:
|
|
200
|
-
|
|
201
|
-
- `ml_kem_keypair_from_seed` — requires a 64-byte `d||z` seed (FIPS 203)
|
|
202
|
-
- `ml_kem_encapsulate_from_seed` — requires a 32-byte seed
|
|
203
|
-
- `ml_dsa_keypair_from_seed` — requires a 32-byte seed
|
|
204
|
-
- `ml_dsa_sign_from_seed` — requires a 32-byte seed
|
|
205
|
-
|
|
206
|
-
These helpers are intended for tests only. They work by installing a
|
|
207
|
-
thread-local seed-replay mode inside the gem's `randombytes()` for
|
|
208
|
-
the duration of the call, then call the stock PQClean entrypoints.
|
|
209
|
-
No internal PQClean algorithm logic is reimplemented in this gem.
|
|
210
|
-
|
|
211
|
-
## Development
|
|
212
|
-
|
|
213
|
-
Run the test suite with:
|
|
84
|
+
PQCrypto.supported_hybrid_kems
|
|
214
85
|
|
|
215
|
-
|
|
216
|
-
|
|
86
|
+
PQCrypto::KEM.generate(:ml_kem_768)
|
|
87
|
+
PQCrypto::Signature.generate(:ml_dsa_65)
|
|
88
|
+
PQCrypto::HybridKEM.generate(:ml_kem_768_x25519_xwing)
|
|
217
89
|
```
|
|
218
90
|
|
|
219
|
-
|
|
220
|
-
update the vendor snapshot. The refresh script has a safe pinned
|
|
221
|
-
default and records the exact vendored snapshot in
|
|
222
|
-
`ext/pqcrypto/vendor/.vendored`:
|
|
91
|
+
## More examples
|
|
223
92
|
|
|
224
|
-
|
|
225
|
-
bundle exec ruby script/vendor_libs.rb
|
|
226
|
-
```
|
|
93
|
+
Detailed usage examples live in [`GET_STARTED.md`](GET_STARTED.md):
|
|
227
94
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
PQCLEAN_STRIP=PQClean-<full-git-commit> \
|
|
236
|
-
bundle exec ruby script/vendor_libs.rb
|
|
237
|
-
```
|
|
95
|
+
- generating keys
|
|
96
|
+
- ML-KEM encapsulation / decapsulation
|
|
97
|
+
- ML-DSA signing / verification
|
|
98
|
+
- streaming ML-DSA for large files
|
|
99
|
+
- SPKI and PKCS#8 serialization
|
|
100
|
+
- `pqc_container_*` compatibility serialization
|
|
101
|
+
- secure wiping and practical safety notes
|
data/SECURITY.md
CHANGED
|
@@ -4,125 +4,144 @@
|
|
|
4
4
|
|
|
5
5
|
`pq_crypto` exposes a primitive-first public surface:
|
|
6
6
|
|
|
7
|
-
- `PQCrypto::KEM`
|
|
8
|
-
- `PQCrypto::Signature`
|
|
9
|
-
- `PQCrypto::HybridKEM`
|
|
7
|
+
- `PQCrypto::KEM` — ML-KEM-512, ML-KEM-768, ML-KEM-1024
|
|
8
|
+
- `PQCrypto::Signature` — ML-DSA-44, ML-DSA-65, ML-DSA-87
|
|
9
|
+
- `PQCrypto::HybridKEM` — ML-KEM-768 + X25519 via the X-Wing combiner
|
|
10
10
|
- `PQCrypto.secure_wipe`
|
|
11
|
-
- `PQCrypto.ct_equals`
|
|
11
|
+
- `PQCrypto.ct_equals`
|
|
12
12
|
|
|
13
|
-
The gem does
|
|
14
|
-
|
|
13
|
+
The gem does not publish protocol/session helpers as part of the supported
|
|
14
|
+
public API.
|
|
15
15
|
|
|
16
16
|
## Audit status
|
|
17
17
|
|
|
18
18
|
This project has not been audited. Treat it as experimental software.
|
|
19
19
|
|
|
20
|
+
The test surface includes deterministic regression tests, NIST ACVP KAT test
|
|
21
|
+
infrastructure, and OpenSSL 3.5+ interoperability tests for standard SPKI /
|
|
22
|
+
PKCS#8 encodings where the linked OpenSSL exposes the corresponding ML-KEM /
|
|
23
|
+
ML-DSA EVP support. These tests improve compatibility coverage but are not a
|
|
24
|
+
substitute for a security audit.
|
|
25
|
+
|
|
20
26
|
## Algorithm notes
|
|
21
27
|
|
|
22
|
-
### ML-KEM
|
|
28
|
+
### ML-KEM / ML-DSA
|
|
23
29
|
|
|
24
|
-
The post-quantum primitives are backed by vendored `PQClean` sources
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
gem.
|
|
30
|
+
The post-quantum primitives are backed by vendored `PQClean` sources and called
|
|
31
|
+
through PQClean's public `crypto_kem_*` and `crypto_sign_*` entrypoints. The gem
|
|
32
|
+
does not reimplement ML-KEM, ML-DSA, SHAKE, or Keccak.
|
|
28
33
|
|
|
29
34
|
### HybridKEM
|
|
30
35
|
|
|
31
|
-
`PQCrypto::HybridKEM` implements the
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
The X-Wing secret decapsulation key is a 32-byte seed. It is expanded
|
|
35
|
-
with SHAKE256 into the ML-KEM-768 and X25519 private material used
|
|
36
|
-
internally for decapsulation. The public key and ciphertext are the
|
|
37
|
-
fixed-length concatenations specified by the draft.
|
|
36
|
+
`PQCrypto::HybridKEM` implements the X-Wing construction from
|
|
37
|
+
`draft-connolly-cfrg-xwing-kem-10`.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
The X-Wing secret decapsulation key is a 32-byte seed. It is expanded with
|
|
40
|
+
SHAKE256 into the ML-KEM-768 and X25519 private material used internally for
|
|
41
|
+
decapsulation. The public key and ciphertext are the fixed-length
|
|
42
|
+
concatenations specified by the draft.
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
```text
|
|
45
|
+
ss = SHA3-256( ss_M || ss_X || ct_X || pk_X || XWingLabel )
|
|
46
|
+
```
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
the strong Diffie-Hellman assumption for X25519 (in the ROM), and
|
|
45
|
-
post-quantum IND-CCA security in the standard model assuming ML-KEM-768
|
|
46
|
-
is IND-CCA secure and SHA3-256 behaves as a PRF.
|
|
48
|
+
where `XWingLabel = "\.//^\"`.
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
interoperability should still be verified against the reference
|
|
50
|
+
External interoperability should be verified against the reference
|
|
50
51
|
implementation before relying on it.
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
## Serialization formats
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
`crypto_sign_keypair` / `crypto_sign_signature` (for ML-DSA) and
|
|
56
|
-
`crypto_kem_keypair_derand` / `crypto_kem_enc_derand` (for ML-KEM)
|
|
57
|
-
against a caller-supplied seed. For ML-DSA, which has no derand API
|
|
58
|
-
upstream, the gem installs a thread-local seed-replay buffer inside
|
|
59
|
-
its `randombytes()` implementation; outside of a test call the same
|
|
60
|
-
`randombytes()` entry delegates directly to OpenSSL `RAND_bytes`. No
|
|
61
|
-
internal PQClean algorithm logic is reimplemented in this gem.
|
|
55
|
+
### pq_crypto-local `pqc_container_*`
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
`pqc_container_*` DER/PEM wrappers are pq_crypto-specific containers. They are:
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
- not ASN.1
|
|
60
|
+
- not SPKI
|
|
61
|
+
- not PKCS#8
|
|
62
|
+
- not advertised as interoperable with OpenSSL, Go, Java, or PKI tooling
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
This format is frozen for backward compatibility and remains limited to the
|
|
65
|
+
original three algorithms:
|
|
68
66
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
67
|
+
- `:ml_kem_768`
|
|
68
|
+
- `:ml_dsa_65`
|
|
69
|
+
- `:ml_kem_768_x25519_xwing`
|
|
70
|
+
|
|
71
|
+
### Standard SPKI / PKCS#8
|
|
72
|
+
|
|
73
|
+
ML-KEM and ML-DSA use standard SPKI public-key and PKCS#8 private-key encodings
|
|
74
|
+
for the NIST parameter sets. AlgorithmIdentifier parameters are absent, not
|
|
75
|
+
encoded as `NULL`.
|
|
76
|
+
|
|
77
|
+
| Algorithm | Standard OID | Reference |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| ML-KEM-512 | `2.16.840.1.101.3.4.4.1` | RFC 9935 |
|
|
80
|
+
| ML-KEM-768 | `2.16.840.1.101.3.4.4.2` | RFC 9935 |
|
|
81
|
+
| ML-KEM-1024 | `2.16.840.1.101.3.4.4.3` | RFC 9935 |
|
|
82
|
+
| ML-DSA-44 | `2.16.840.1.101.3.4.3.17` | RFC 9881 |
|
|
83
|
+
| ML-DSA-65 | `2.16.840.1.101.3.4.3.18` | RFC 9881 |
|
|
84
|
+
| ML-DSA-87 | `2.16.840.1.101.3.4.3.19` | RFC 9881 |
|
|
85
|
+
|
|
86
|
+
`PQCrypto::KEM.details` / `PQCrypto::Signature.details` keep `:oid` as the
|
|
87
|
+
legacy `pqc_container_*` OID for backward compatibility. Use
|
|
88
|
+
`PQCrypto::AlgorithmRegistry.standard_oid` for the standard OID.
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
ML-DSA currently use project-local UUID-derived OIDs under `2.25.*`.
|
|
75
|
-
Hybrid X-Wing uses the draft X-Wing OID `1.3.6.1.4.1.62253.25722`.
|
|
90
|
+
## ML-DSA seed-format imports
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
ML-DSA seed and both-form PKCS#8 imports are disabled by default. To import
|
|
93
|
+
these encodings, callers must explicitly set:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
PQCrypto::PKCS8.allow_ml_dsa_seed_format = true
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This opt-in exists because PQClean exposes no public ML-DSA
|
|
100
|
+
`crypto_sign_keypair_derand` entrypoint. The implementation therefore reuses the
|
|
101
|
+
same thread-local seed-replay `randombytes()` path introduced for KAT tests to
|
|
102
|
+
expand the RFC 9881 seed into an expanded private key. The replay buffer is
|
|
103
|
+
thread-local, cleared immediately after expansion, and remains inactive for all
|
|
104
|
+
normal production randomness paths.
|
|
105
|
+
|
|
106
|
+
For `both` encodings, the decoder expands the seed and rejects the key if the
|
|
107
|
+
expandedKey half does not match the seed-derived key.
|
|
108
|
+
|
|
109
|
+
## Deterministic test hooks
|
|
110
|
+
|
|
111
|
+
`PQCrypto::Testing` deterministic helpers drive the stock PQClean entrypoints
|
|
112
|
+
against caller-supplied seeds. For ML-DSA, which has no derand API upstream, the
|
|
113
|
+
gem installs a thread-local seed-replay buffer inside its `randombytes()`
|
|
114
|
+
implementation; outside of a test call the same `randombytes()` entry delegates
|
|
115
|
+
directly to OpenSSL `RAND_bytes`.
|
|
83
116
|
|
|
84
117
|
## Memory wiping
|
|
85
118
|
|
|
86
|
-
`PQCrypto.secure_wipe` clears mutable Ruby strings in place. Ruby key
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
`PQCrypto.secure_wipe` clears mutable Ruby strings in place. Ruby key objects
|
|
120
|
+
take a copy of the bytes passed into their constructor and expose `#wipe!` to
|
|
121
|
+
zero only that internal copy. Ruby garbage collection and prior derived copies
|
|
122
|
+
may still leave sensitive material elsewhere in process memory.
|
|
123
|
+
|
|
124
|
+
Secret key objects redact `inspect` output and intentionally do not expose a
|
|
125
|
+
public `fingerprint` method. This avoids accidental logging of raw secret bytes
|
|
126
|
+
or stable secret-derived identifiers.
|
|
92
127
|
|
|
93
128
|
## OpenSSL baseline
|
|
94
129
|
|
|
95
|
-
`pq_crypto` requires OpenSSL
|
|
130
|
+
`pq_crypto` requires OpenSSL 3.0 or later.
|
|
96
131
|
|
|
97
132
|
OpenSSL is used for:
|
|
98
133
|
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
- Base64 encode/decode for PEM via OpenSSL
|
|
104
|
-
header/footer framing and trailing-garbage checks.
|
|
105
|
-
|
|
106
|
-
## Secret key display and wiping
|
|
107
|
-
|
|
108
|
-
Secret key objects redact `inspect` output and intentionally do not expose
|
|
109
|
-
a public `fingerprint` method. This avoids accidental logging of raw secret
|
|
110
|
-
bytes or stable secret-derived identifiers.
|
|
134
|
+
- X25519 key generation and key agreement
|
|
135
|
+
- SHA3-256 for the X-Wing combiner
|
|
136
|
+
- RAND_bytes as the production entropy source for `randombytes()`
|
|
137
|
+
- CRYPTO_memcmp for constant-time comparison
|
|
138
|
+
- Base64 encode/decode for PEM via OpenSSL BIOs
|
|
111
139
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
OpenSSL, native wrapper buffers, serialization, logging, crash dumps, or
|
|
115
|
-
the garbage collector.
|
|
140
|
+
OpenSSL 3.5+ is additionally used in interop tests when ML-KEM / ML-DSA EVP
|
|
141
|
+
support is available.
|
|
116
142
|
|
|
117
143
|
## Threading
|
|
118
144
|
|
|
119
145
|
Concurrent read-only operations on primitive key objects are supported.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
pinned in place.
|
|
123
|
-
|
|
124
|
-
The deterministic test hooks use a thread-local seed-replay mode
|
|
125
|
-
around `randombytes()`, so a test running on one thread does not
|
|
126
|
-
affect production callers on other threads. The deterministic helpers
|
|
127
|
-
remain test-only utilities and should not be relied on as a general
|
|
128
|
-
multi-threading contract.
|
|
146
|
+
Mutating operations such as `wipe!` must not race with other uses of the same
|
|
147
|
+
object.
|
data/ext/pqcrypto/extconf.rb
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "mkmf"
|
|
5
|
+
require_relative "../../lib/pq_crypto/version"
|
|
6
|
+
|
|
7
|
+
def generate_version_header!
|
|
8
|
+
version = PQCrypto::VERSION
|
|
9
|
+
unless version.match?(/\A[0-9A-Za-z][0-9A-Za-z._+-]*\z/)
|
|
10
|
+
abort "Invalid PQCrypto::VERSION for C header: #{version.inspect}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
header = File.join(__dir__, "pqcrypto_version.h")
|
|
14
|
+
File.write(header, <<~C)
|
|
15
|
+
/* Generated by extconf.rb from lib/pq_crypto/version.rb. Do not edit. */
|
|
16
|
+
#ifndef PQCRYPTO_VERSION_H
|
|
17
|
+
#define PQCRYPTO_VERSION_H
|
|
18
|
+
|
|
19
|
+
#define PQCRYPTO_VERSION #{version.dump}
|
|
20
|
+
|
|
21
|
+
#endif
|
|
22
|
+
C
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
generate_version_header!
|
|
5
26
|
|
|
6
27
|
$CFLAGS << " -std=c11 -Wall -Wextra -O2"
|
|
7
28
|
$CFLAGS << " -fstack-protector-strong -D_FORTIFY_SOURCE=2"
|
|
@@ -11,6 +32,9 @@ $LDFLAGS << " -Wl,-no_warn_duplicate_libraries" if RbConfig::CONFIG["host_os"] =
|
|
|
11
32
|
|
|
12
33
|
USE_SYSTEM = arg_config("--use-system-libraries") || ENV["PQCRYPTO_USE_SYSTEM_LIBRARIES"]
|
|
13
34
|
|
|
35
|
+
KECCAK_BACKEND = (ENV["PQCRYPTO_KECCAK_BACKEND"] || "clean").strip.downcase
|
|
36
|
+
SUPPORTED_KECCAK_BACKENDS = %w[clean xkcp].freeze
|
|
37
|
+
|
|
14
38
|
SANITIZE = ENV["PQCRYPTO_SANITIZE"]
|
|
15
39
|
|
|
16
40
|
if SANITIZE && !SANITIZE.strip.empty?
|
|
@@ -85,27 +109,77 @@ def configure_openssl!
|
|
|
85
109
|
$CFLAGS << " -DHAVE_OPENSSL_EVP_H -DHAVE_OPENSSL_RAND_H"
|
|
86
110
|
end
|
|
87
111
|
|
|
112
|
+
def configure_keccak_backend(vendor_dir, common_dir)
|
|
113
|
+
abort "Unsupported PQCRYPTO_KECCAK_BACKEND=#{KECCAK_BACKEND.inspect}. Supported: #{SUPPORTED_KECCAK_BACKENDS.join(", ")}" unless SUPPORTED_KECCAK_BACKENDS.include?(KECCAK_BACKEND)
|
|
114
|
+
|
|
115
|
+
case KECCAK_BACKEND
|
|
116
|
+
when "clean"
|
|
117
|
+
{
|
|
118
|
+
name: "clean",
|
|
119
|
+
include_dirs: [],
|
|
120
|
+
source_group: ["pqclean_common", [File.join(common_dir, "fips202.c")]]
|
|
121
|
+
}
|
|
122
|
+
when "xkcp"
|
|
123
|
+
# The optimized backend must provide the same fips202.h-compatible API as
|
|
124
|
+
# PQClean's common/fips202.c. Do not substitute OpenSSL EVP SHAKE here: the
|
|
125
|
+
# PQClean SHAKE state layout is part of the ML-KEM/ML-DSA call graph.
|
|
126
|
+
xkcp_dir = File.join(vendor_dir, "xkcp")
|
|
127
|
+
adapter_source = File.join(xkcp_dir, "pqclean_fips202_xkcp.c")
|
|
128
|
+
|
|
129
|
+
abort <<~MSG unless File.exist?(adapter_source)
|
|
130
|
+
PQCRYPTO_KECCAK_BACKEND=xkcp was requested, but no reviewed XKCP adapter was found.
|
|
131
|
+
|
|
132
|
+
Expected:
|
|
133
|
+
#{adapter_source}
|
|
134
|
+
|
|
135
|
+
Refusing to fall back silently to the clean backend. Vendor a fips202.h-compatible
|
|
136
|
+
XKCP adapter first, then run the full SHAKE-dependent KAT/regression test matrix.
|
|
137
|
+
MSG
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
name: "xkcp",
|
|
141
|
+
include_dirs: [xkcp_dir],
|
|
142
|
+
source_group: ["xkcp_keccak", [adapter_source]]
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
88
147
|
def configure_pqclean(vendor_dir)
|
|
89
148
|
return nil unless vendor_dir
|
|
90
149
|
|
|
91
150
|
pqclean_dir = File.join(vendor_dir, "pqclean")
|
|
92
151
|
return nil unless Dir.exist?(pqclean_dir)
|
|
93
152
|
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
mlkem_dirs = {
|
|
154
|
+
"pqclean_mlkem512" => File.join(pqclean_dir, "crypto_kem", "ml-kem-512", "clean"),
|
|
155
|
+
"pqclean_mlkem768" => File.join(pqclean_dir, "crypto_kem", "ml-kem-768", "clean"),
|
|
156
|
+
"pqclean_mlkem1024" => File.join(pqclean_dir, "crypto_kem", "ml-kem-1024", "clean")
|
|
157
|
+
}
|
|
158
|
+
mldsa_dirs = {
|
|
159
|
+
"pqclean_mldsa44" => File.join(pqclean_dir, "crypto_sign", "ml-dsa-44", "clean"),
|
|
160
|
+
"pqclean_mldsa65" => File.join(pqclean_dir, "crypto_sign", "ml-dsa-65", "clean"),
|
|
161
|
+
"pqclean_mldsa87" => File.join(pqclean_dir, "crypto_sign", "ml-dsa-87", "clean")
|
|
162
|
+
}
|
|
96
163
|
common_dir = File.join(pqclean_dir, "common")
|
|
97
164
|
|
|
98
|
-
|
|
165
|
+
keccak_config = configure_keccak_backend(vendor_dir, common_dir)
|
|
166
|
+
|
|
167
|
+
include_dirs = [*mlkem_dirs.values, *mldsa_dirs.values, common_dir, *keccak_config[:include_dirs]]
|
|
99
168
|
return nil unless include_dirs.all? { |dir| Dir.exist?(dir) }
|
|
100
169
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
170
|
+
mlkem_source_groups = mlkem_dirs.map do |prefix, dir|
|
|
171
|
+
[prefix, Dir.glob(File.join(dir, "*.c")).sort]
|
|
172
|
+
end
|
|
173
|
+
mldsa_source_groups = mldsa_dirs.map do |prefix, dir|
|
|
174
|
+
[prefix, Dir.glob(File.join(dir, "*.c")).sort]
|
|
175
|
+
end
|
|
176
|
+
common_sources = %w[sha2.c sp800-185.c].map { |name| File.join(common_dir, name) }
|
|
104
177
|
|
|
105
178
|
source_groups = [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
["pqclean_common", common_sources]
|
|
179
|
+
*mlkem_source_groups,
|
|
180
|
+
*mldsa_source_groups,
|
|
181
|
+
["pqclean_common", common_sources],
|
|
182
|
+
keccak_config[:source_group]
|
|
109
183
|
]
|
|
110
184
|
|
|
111
185
|
return nil unless source_groups.all? { |_, sources| sources.all? { |path| File.exist?(path) } }
|
|
@@ -115,6 +189,7 @@ def configure_pqclean(vendor_dir)
|
|
|
115
189
|
|
|
116
190
|
{
|
|
117
191
|
include_dirs: include_dirs,
|
|
192
|
+
keccak_backend: keccak_config[:name],
|
|
118
193
|
source_groups: source_groups
|
|
119
194
|
}
|
|
120
195
|
end
|
|
@@ -165,6 +240,7 @@ pqclean_config = configure_pqclean(vendor_dir)
|
|
|
165
240
|
puts "OpenSSL: system"
|
|
166
241
|
abort "PQClean vendored sources are required. Run: bundle exec rake vendor" unless pqclean_config
|
|
167
242
|
puts "PQClean: vendored (randombytes overridden by pq_randombytes.c)"
|
|
243
|
+
puts "Keccak backend: #{pqclean_config[:keccak_backend]}"
|
|
168
244
|
puts "Output: pqcrypto/pqcrypto_secure"
|
|
169
245
|
puts "===================================="
|
|
170
246
|
|