pq_crypto 0.6.6 → 0.6.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 273b83c96db2314540118b933a9f70a22c954e59a6fb38377f7c6cd1f337d698
4
- data.tar.gz: 0a6f4b88a3aefde58049f41f582548aa10f2aa939e98a8b60791ff23c8a5be0e
3
+ metadata.gz: cf8d3b17b9579ef551ef337aab16a9eb356846a36ff492577e2d728ec0e285e2
4
+ data.tar.gz: 26c3c3646d066617e37cb2cb1219e7ee5ca640488c84b480b6259461999b48e5
5
5
  SHA512:
6
- metadata.gz: f4905490a34b75ae7548a896cbb379b135c7615dfba9dca564add76892fb6074a6e0a12220ca3158417ea3264c2dcb2dc432b0a49fa91f8429eaebeef966d895
7
- data.tar.gz: 854941b5a331956cba2aa91ef1777148f9a93713eec6ce746fe01475b126b69f409ad0dbcf631a1db5c7c09d127d55053d59585850bcf06a8047f8b2ae14adec
6
+ metadata.gz: bd58150566a566af1c3083ef2c72538acafb6b15c5ebe954d0d7af35f96cdf41a5f1fe17c8d3c956e29b3c51bd89b29ec24245749efbca82389b1d2dc3c91ede
7
+ data.tar.gz: 5d974df3a99f9284da5c5cdfff25ba1808dc083063c12f97db6b803a34aa15687a0cda9565c8617edd95015c8d23139181d56d69d2483a3ac17b2239bec3cba3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,104 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.7] - 2026-08-10
4
+
5
+ ### Fixed
6
+
7
+ - **DER parsers could raise exceptions outside the `PQCrypto::Error` hierarchy.**
8
+ `OpenSSL::ASN1.decode` raises more than `OpenSSL::ASN1::ASN1Error`, and
9
+ re-encoding a successfully decoded object can fail as well. Three shapes
10
+ escaped the previous handling in `PQCrypto::SPKI`:
11
+
12
+ - `"\x0A\x01\xFF"` (ENUMERATED) raised `OpenSSL::OpenSSLError`
13
+ - `"\x17\x00"` / `"\x18\x00"` (empty UTCTIME / GENERALIZEDTIME) raised `TypeError`
14
+ - `"\x10\x00"` (universal SEQUENCE tag without the constructed bit) decoded
15
+ cleanly but raised `TypeError` from the `to_der` round trip used for the
16
+ trailing-data check
17
+
18
+ `TypeError` does not descend from `OpenSSL::OpenSSLError`, so widening the
19
+ rescue to the OpenSSL base class alone was not sufficient. Both rescue sites
20
+ now catch `OpenSSL::OpenSSLError, TypeError`, and `SPKI.decode_der` is guarded
21
+ end to end rather than only around the decode call.
22
+
23
+ Impact: code written as `rescue PQCrypto::Error` around key loading could be
24
+ bypassed by malformed input. No memory-safety consequence. Covered by a
25
+ systematic tag sweep over 1,280 DER shapes × three entry points
26
+ (`Key.from_der`, `SPKI.decode_der`, `PKCS8.decode_der`).
27
+
28
+ ### Added
29
+
30
+ - **FIPS 203 key validation for ML-KEM (boundary diagnostics).** mlkem-native
31
+ v2 exposes `check_pk` and `check_sk`, which this gem did not use. Both are
32
+ now wired in and run when a `PQCrypto::KEM::PublicKey` or `SecretKey` is
33
+ constructed:
34
+
35
+ - public keys get the Section 7.2 modulus check (coefficients in `[0,q-1]`)
36
+ - secret keys get the Section 7.3 hash check (`H(pk)` embedded in the sk)
37
+
38
+ Scope limits (intentional, matching FIPS 203):
39
+
40
+ - these checks are **diagnostics at import**, not a substitute for the
41
+ checks already performed inside encapsulation / decapsulation
42
+ - Section 7.3 does **not** detect a corrupted IND-CPA secret (`dk`) when
43
+ the embedded `H(pk)` is still intact — `#valid?` can still be true
44
+ - X-Wing public keys validate only the ML-KEM-768 half; the X25519 half is
45
+ unrestricted. X-Wing secret keys are 32-byte seeds and are not structure-
46
+ checked
47
+
48
+ `#valid?` is exposed on both key classes. Failures raise
49
+ `PQCrypto::InvalidKeyError`. Public-key internal buffers are frozen after
50
+ construction to block post-validation mutation; secret-key buffers stay
51
+ mutable so `#wipe!` can still zero them in place.
52
+
53
+ New C API: `pq_mlkem{,512,1024}_check_public_key` / `_check_secret_key`,
54
+ plus `PQ_ERROR_INVALID_PUBLIC_KEY` (-11) and `PQ_ERROR_INVALID_SECRET_KEY`
55
+ (-12). Upstream OOM (`-2`) is mapped to `PQ_ERROR_NOMEM` rather than
56
+ “invalid key”.
57
+
58
+ - **ML-DSA secret-key structure check on import.** Expanded ML-DSA secret
59
+ keys are validated via upstream `pk_from_sk` (coefficient norms, recomputed
60
+ `t0`, and `tr = H(pk)`). This matches the check mldsa-native documents for
61
+ importers and is enforced on `Signature::SecretKey` construction with
62
+ `#valid?` exposed. Public keys remain length-checked only (FIPS 204 has no
63
+ ML-KEM-style modulus check for the public key).
64
+
65
+ The check runs on import only. Keys produced by this process's own key
66
+ generation are valid by construction, and `pk_from_sk` re-derives the public
67
+ key, so validating them would roughly double the cost of every ML-DSA
68
+ keypair. Upstream also documents `pk_from_sk` as leaking secret-key validity
69
+ through its return value and timing.
70
+
71
+ New C API: `pq_mldsa{,44,87}_check_secret_key`.
72
+
73
+ - **`PQCRYPTO_KEYGEN_PCT=1` build option.** Enables the FIPS 140-3 pairwise
74
+ consistency test in both backends (`MLK_CONFIG_KEYGEN_PCT` /
75
+ `MLD_CONFIG_KEYGEN_PCT`): after generating a keypair it is exercised to catch
76
+ a faulty RNG or memory corruption at generation time. Upstream ships this
77
+ opt-in because it makes key generation noticeably more expensive, and it stays
78
+ opt-in here for the same reason. The selected setting is reported in the
79
+ build configuration banner.
80
+
81
+ - **`require_encrypted:` for PKCS#8 loading.** Supplying a passphrase states an
82
+ expectation that the key at rest is encrypted, but an unencrypted
83
+ PrivateKeyInfo was previously accepted and the passphrase left unused. An
84
+ encrypted key file replaced with a plaintext one therefore loaded without
85
+ complaint.
86
+
87
+ `require_encrypted: true` enforces that the input is an
88
+ EncryptedPrivateKeyInfo (DER content, not PEM label — relabeling a plaintext
89
+ key as `ENCRYPTED PRIVATE KEY` does not satisfy the policy). Available on
90
+ `PKCS8.decode_der` / `decode_pem`, `Key.from_der` / `from_pem`, and
91
+ `secret_key_from_pkcs8_der` / `_pem` on both `KEM` and `Signature`.
92
+
93
+ Limits (documented so the flag is not over-read):
94
+
95
+ - default is `false`; other loaders (`from_bytes`, `pqc_container_*`) are
96
+ unchanged
97
+ - the flag checks **format**, not passphrase strength (empty passphrase and
98
+ low PBKDF iterations still count as encrypted)
99
+ - the value must be a real boolean (`true`/`false`); strings like `"false"`
100
+ raise `ArgumentError` instead of being treated as truthy
101
+
3
102
  ## [0.6.6] - 2026-08-10
4
103
 
5
104
  ### Changed
data/README.md CHANGED
@@ -101,6 +101,13 @@ can be tested explicitly with:
101
101
  PQCRYPTO_NATIVE_ASM=1 bundle exec rake compile
102
102
  ```
103
103
 
104
+ The FIPS 140-3 pairwise consistency test is available but off by default,
105
+ because it makes key generation noticeably more expensive:
106
+
107
+ ```bash
108
+ PQCRYPTO_KEYGEN_PCT=1 bundle exec rake compile
109
+ ```
110
+
104
111
  ## Security status
105
112
 
106
113
  `pq_crypto` is experimental and not audited. Treat it as a low-level primitive
@@ -125,8 +132,12 @@ PQCrypto::Signature.secret_key_from_seed(:ml_dsa_65, seed_32_bytes)
125
132
 
126
133
  PQCrypto::HybridKEM.generate(:ml_kem_768_x25519_xwing)
127
134
  PQCrypto::Key.from_pem(pem, passphrase: passphrase)
135
+ PQCrypto::Key.from_pem(pem, passphrase: passphrase, require_encrypted: true)
136
+ keypair.public_key.valid? # ML-KEM structure check; ML-DSA public keys are length-only
137
+ keypair.secret_key.valid?
128
138
  ```
129
139
 
140
+
130
141
  ## More examples
131
142
 
132
143
  Detailed usage examples live in [`GET_STARTED.md`](GET_STARTED.md):
data/SECURITY.md CHANGED
@@ -127,6 +127,52 @@ into `mldsa-native` `signature_internal`; for an empty context this prefix is
127
127
  Outside of test-only deterministic calls, production randomness delegates
128
128
  directly to OpenSSL `RAND_bytes`.
129
129
 
130
+ ## Key import validation (0.6.7+)
131
+
132
+ ML-KEM public/secret keys and ML-DSA secret keys are structure-checked when
133
+ the corresponding Ruby key objects are constructed:
134
+
135
+ | Object | Check | Source |
136
+ | --- | --- | --- |
137
+ | `KEM::PublicKey` | FIPS 203 §7.2 modulus check | `check_pk` |
138
+ | `KEM::SecretKey` | FIPS 203 §7.3 `H(pk)` check | `check_sk` |
139
+ | `Signature::SecretKey` | norms / `t0` / `tr` consistency | `pk_from_sk` |
140
+ | `Signature::PublicKey` | length only | — |
141
+ | X-Wing public key | ML-KEM-768 half only | `check_pk` |
142
+ | X-Wing secret key | none (32-byte seed) | — |
143
+
144
+ These are **import diagnostics**. Encapsulation, decapsulation, signing, and
145
+ verification still apply their own backend checks. A true `#valid?` result
146
+ does not prove full cryptographic integrity of every field (for example an
147
+ ML-KEM secret key with a corrupted IND-CPA `dk` but intact `H(pk)` still
148
+ passes §7.3).
149
+
150
+ The checks run on import. Keys produced by this process's own key generation
151
+ are valid by construction and are not re-checked, because the ML-DSA check
152
+ re-derives the public key and would roughly double the cost of every keypair.
153
+
154
+ The ML-DSA check (`pk_from_sk`) is documented upstream as leaking whether the
155
+ secret key is valid, through both its return value and its timing.
156
+
157
+ `require_encrypted:` applies only to the PKCS#8 branch. `PQCrypto::Key.from_der`
158
+ and `.from_pem` also accept SPKI public keys, and for those inputs the flag has
159
+ no effect and is silently ignored.
160
+
161
+ Public-key internal buffers are frozen after construction. Secret-key buffers
162
+ remain mutable so `#wipe!` can zero them.
163
+
164
+ ### `require_encrypted:`
165
+
166
+ `require_encrypted: true` on PKCS#8 loaders rejects plaintext PrivateKeyInfo
167
+ and requires EncryptedPrivateKeyInfo (classified from DER, not PEM labels).
168
+ It does **not**:
169
+
170
+ - apply to `from_bytes` / `pqc_container_*` loaders
171
+ - enforce passphrase quality or PBKDF iteration count
172
+ - default on (default remains `false`)
173
+
174
+ The flag must be a boolean; non-boolean values raise `ArgumentError`.
175
+
130
176
  ## Memory wiping
131
177
 
132
178
  `PQCrypto.secure_wipe` clears mutable Ruby strings in place. Ruby key objects
@@ -84,6 +84,8 @@ def env_bool(name, default)
84
84
  end
85
85
  end
86
86
 
87
+ KEYGEN_PCT = env_bool("PQCRYPTO_KEYGEN_PCT", false)
88
+
87
89
  NATIVE_ASM = env_bool("PQCRYPTO_NATIVE_ASM", native_asm_supported_by_default?)
88
90
  NATIVE_ARITH = env_bool("PQCRYPTO_NATIVE_ARITH", NATIVE_ASM)
89
91
  NATIVE_FIPS202 = env_bool("PQCRYPTO_NATIVE_FIPS202", NATIVE_ASM)
@@ -529,6 +531,7 @@ def native_flags(kind, level, shared:)
529
531
  # Upstream has no guard against the stale define, so passing it would be a
530
532
  # silently dead -D rather than a build error. Do not reintroduce it.
531
533
  flags << (shared ? "-D#{prefix}_CONFIG_MULTILEVEL_WITH_SHARED" : "-D#{prefix}_CONFIG_MULTILEVEL_NO_SHARED")
534
+ flags << "-D#{prefix}_CONFIG_KEYGEN_PCT" if KEYGEN_PCT
532
535
  flags << "-D#{prefix}_CONFIG_USE_NATIVE_BACKEND_ARITH" if NATIVE_ARITH
533
536
  flags << "-D#{prefix}_CONFIG_USE_NATIVE_BACKEND_FIPS202" if NATIVE_FIPS202
534
537
  flags.join(" ")
@@ -615,6 +618,7 @@ puts "Vendor ASM arch flags: #{VENDOR_ASM_ARCH_FLAGS.empty? ? '(none)' : VENDOR_
615
618
  if x86_64_host? && (NATIVE_ARITH || NATIVE_FIPS202)
616
619
  puts "x86_64 native backend: AVX2 build flags enabled"
617
620
  end
621
+ puts "Keygen PCT (FIPS 140-3): #{KEYGEN_PCT ? 'enabled' : 'disabled'}"
618
622
  puts "PQClean fallback: removed"
619
623
  puts "Output: pqcrypto/pqcrypto_secure"
620
624
  puts "===================================="
@@ -78,18 +78,24 @@ int pqcr_mlkem512_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins)
78
78
  int pqcr_mlkem512_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
79
79
  int pqcr_mlkem512_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
80
80
  int pqcr_mlkem512_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
81
+ int pqcr_mlkem512_check_pk(const uint8_t *pk);
82
+ int pqcr_mlkem512_check_sk(const uint8_t *sk);
81
83
 
82
84
  int pqcr_mlkem768_keypair(uint8_t *pk, uint8_t *sk);
83
85
  int pqcr_mlkem768_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins);
84
86
  int pqcr_mlkem768_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
85
87
  int pqcr_mlkem768_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
86
88
  int pqcr_mlkem768_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
89
+ int pqcr_mlkem768_check_pk(const uint8_t *pk);
90
+ int pqcr_mlkem768_check_sk(const uint8_t *sk);
87
91
 
88
92
  int pqcr_mlkem1024_keypair(uint8_t *pk, uint8_t *sk);
89
93
  int pqcr_mlkem1024_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins);
90
94
  int pqcr_mlkem1024_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
91
95
  int pqcr_mlkem1024_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
92
96
  int pqcr_mlkem1024_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
97
+ int pqcr_mlkem1024_check_pk(const uint8_t *pk);
98
+ int pqcr_mlkem1024_check_sk(const uint8_t *sk);
93
99
 
94
100
  void pqcr_mlkem_shake256(uint8_t *output, size_t outlen, const uint8_t *input, size_t inlen);
95
101
  void pqcr_mlkem_sha3_256(uint8_t *output, const uint8_t *input, size_t inlen);
@@ -718,6 +718,12 @@ __attribute__((noreturn)) static void pq_raise_general_error(int err) {
718
718
  case PQ_ERROR_OPENSSL:
719
719
  rb_raise(ePQCryptoError, "OpenSSL error");
720
720
  break;
721
+ case PQ_ERROR_INVALID_PUBLIC_KEY:
722
+ rb_raise(ePQCryptoError, "Invalid public key");
723
+ break;
724
+ case PQ_ERROR_INVALID_SECRET_KEY:
725
+ rb_raise(ePQCryptoError, "Invalid secret key");
726
+ break;
721
727
  default:
722
728
  rb_raise(ePQCryptoError, "Unknown error: %d", err);
723
729
  break;
@@ -765,6 +771,28 @@ static VALUE pq_run_kem_keypair_from_seed(void *(*nogvl)(void *), VALUE seed, si
765
771
  X(ml_kem_1024, MLKEM1024_PUBLICKEYBYTES, MLKEM1024_SECRETKEYBYTES, MLKEM1024_CIPHERTEXTBYTES, \
766
772
  MLKEM1024_SHAREDSECRETBYTES)
767
773
 
774
+ static VALUE pq_run_key_check(int (*check)(const uint8_t *), VALUE key, size_t expected_len) {
775
+ uint8_t *buffer;
776
+ size_t copied_len = 0;
777
+ int result;
778
+
779
+ StringValue(key);
780
+ if ((size_t)RSTRING_LEN(key) != expected_len) {
781
+ return Qfalse;
782
+ }
783
+
784
+ buffer = pq_copy_ruby_string(key, &copied_len);
785
+ result = check(buffer);
786
+ pq_secure_wipe(buffer, copied_len);
787
+ free(buffer);
788
+
789
+ if (result == PQ_ERROR_NOMEM) {
790
+ rb_raise(rb_eNoMemError, "Memory allocation failed");
791
+ }
792
+
793
+ return result == PQ_SUCCESS ? Qtrue : Qfalse;
794
+ }
795
+
768
796
  #define PQ_DEFINE_RUBY_KEM(rb_name, pk_bytes, sk_bytes, ct_bytes, ss_bytes) \
769
797
  static VALUE pqcrypto_##rb_name##_keypair(VALUE self) { \
770
798
  (void)self; \
@@ -787,6 +815,41 @@ static VALUE pq_run_kem_keypair_from_seed(void *(*nogvl)(void *), VALUE seed, si
787
815
  secret_key, sk_bytes, ss_bytes); \
788
816
  }
789
817
 
818
+ #define PQ_DEFINE_RUBY_KEM_CHECK(rb_name, c_name) \
819
+ static VALUE pqcrypto_##rb_name##_check_public_key(VALUE self, VALUE key) { \
820
+ (void)self; \
821
+ return pq_run_key_check(pq_##c_name##_check_public_key, key, \
822
+ (size_t)c_name##_PUBLICKEYBYTES_VALUE); \
823
+ } \
824
+ static VALUE pqcrypto_##rb_name##_check_secret_key(VALUE self, VALUE key) { \
825
+ (void)self; \
826
+ return pq_run_key_check(pq_##c_name##_check_secret_key, key, \
827
+ (size_t)c_name##_SECRETKEYBYTES_VALUE); \
828
+ }
829
+
830
+ #define mlkem_PUBLICKEYBYTES_VALUE PQ_MLKEM_PUBLICKEYBYTES
831
+ #define mlkem_SECRETKEYBYTES_VALUE PQ_MLKEM_SECRETKEYBYTES
832
+ #define mlkem512_PUBLICKEYBYTES_VALUE MLKEM512_PUBLICKEYBYTES
833
+ #define mlkem512_SECRETKEYBYTES_VALUE MLKEM512_SECRETKEYBYTES
834
+ #define mlkem1024_PUBLICKEYBYTES_VALUE MLKEM1024_PUBLICKEYBYTES
835
+ #define mlkem1024_SECRETKEYBYTES_VALUE MLKEM1024_SECRETKEYBYTES
836
+
837
+ PQ_KEM_KEYPAIR_NOGVL_VARIANTS(PQ_DEFINE_RUBY_KEM_CHECK)
838
+
839
+ #undef PQ_DEFINE_RUBY_KEM_CHECK
840
+
841
+ #define PQ_DEFINE_RUBY_MLDSA_CHECK_SK(rb_name, c_name, sk_bytes) \
842
+ static VALUE pqcrypto_##rb_name##_check_secret_key(VALUE self, VALUE key) { \
843
+ (void)self; \
844
+ return pq_run_key_check(pq_##c_name##_check_secret_key, key, (size_t)(sk_bytes)); \
845
+ }
846
+
847
+ PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa, mldsa, MLDSA65_SECRETKEYBYTES)
848
+ PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa_44, mldsa44, MLDSA44_SECRETKEYBYTES)
849
+ PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa_87, mldsa87, MLDSA87_SECRETKEYBYTES)
850
+
851
+ #undef PQ_DEFINE_RUBY_MLDSA_CHECK_SK
852
+
790
853
  PQ_RUBY_KEM_VARIANTS(PQ_DEFINE_RUBY_KEM)
791
854
 
792
855
  #undef PQ_DEFINE_RUBY_KEM
@@ -1999,6 +2062,24 @@ void Init_pqcrypto_secure(void) {
1999
2062
  pqcrypto_ml_kem_1024_encapsulate, 1);
2000
2063
  rb_define_module_function(mPQCrypto, "ml_kem_1024_decapsulate",
2001
2064
  pqcrypto_ml_kem_1024_decapsulate, 2);
2065
+ rb_define_module_function(mPQCrypto, "ml_kem_check_public_key",
2066
+ pqcrypto_ml_kem_check_public_key, 1);
2067
+ rb_define_module_function(mPQCrypto, "ml_kem_check_secret_key",
2068
+ pqcrypto_ml_kem_check_secret_key, 1);
2069
+ rb_define_module_function(mPQCrypto, "ml_kem_512_check_public_key",
2070
+ pqcrypto_ml_kem_512_check_public_key, 1);
2071
+ rb_define_module_function(mPQCrypto, "ml_kem_512_check_secret_key",
2072
+ pqcrypto_ml_kem_512_check_secret_key, 1);
2073
+ rb_define_module_function(mPQCrypto, "ml_kem_1024_check_public_key",
2074
+ pqcrypto_ml_kem_1024_check_public_key, 1);
2075
+ rb_define_module_function(mPQCrypto, "ml_kem_1024_check_secret_key",
2076
+ pqcrypto_ml_kem_1024_check_secret_key, 1);
2077
+ rb_define_module_function(mPQCrypto, "ml_dsa_check_secret_key",
2078
+ pqcrypto_ml_dsa_check_secret_key, 1);
2079
+ rb_define_module_function(mPQCrypto, "ml_dsa_44_check_secret_key",
2080
+ pqcrypto_ml_dsa_44_check_secret_key, 1);
2081
+ rb_define_module_function(mPQCrypto, "ml_dsa_87_check_secret_key",
2082
+ pqcrypto_ml_dsa_87_check_secret_key, 1);
2002
2083
  rb_define_module_function(mPQCrypto, "hybrid_kem_keypair", pqcrypto_hybrid_kem_keypair, 0);
2003
2084
  rb_define_module_function(mPQCrypto, "hybrid_kem_encapsulate", pqcrypto_hybrid_kem_encapsulate,
2004
2085
  1);
@@ -265,12 +265,64 @@ cleanup:
265
265
  return PQ_ERROR_BUFFER; \
266
266
  } \
267
267
  return native##_dec(ss, ct, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_DECAPSULATE; \
268
+ } \
269
+ int pq_##prefix##_check_public_key(const uint8_t *pk) { \
270
+ int rc; \
271
+ if (!pk) { \
272
+ return PQ_ERROR_BUFFER; \
273
+ } \
274
+ rc = native##_check_pk(pk); \
275
+ if (rc == 0) { \
276
+ return PQ_SUCCESS; \
277
+ } \
278
+ if (rc == -2) { \
279
+ return PQ_ERROR_NOMEM; \
280
+ } \
281
+ return PQ_ERROR_INVALID_PUBLIC_KEY; \
282
+ } \
283
+ int pq_##prefix##_check_secret_key(const uint8_t *sk) { \
284
+ int rc; \
285
+ if (!sk) { \
286
+ return PQ_ERROR_BUFFER; \
287
+ } \
288
+ rc = native##_check_sk(sk); \
289
+ if (rc == 0) { \
290
+ return PQ_SUCCESS; \
291
+ } \
292
+ if (rc == -2) { \
293
+ return PQ_ERROR_NOMEM; \
294
+ } \
295
+ return PQ_ERROR_INVALID_SECRET_KEY; \
268
296
  }
269
297
 
270
298
  PQ_MLKEM_VARIANTS(PQ_DEFINE_MLKEM_SHIMS)
271
299
 
272
300
  #undef PQ_DEFINE_MLKEM_SHIMS
273
301
 
302
+ #define PQ_DEFINE_MLDSA_CHECK_SK(prefix, native, pk_bytes) \
303
+ int pq_##prefix##_check_secret_key(const uint8_t *sk) { \
304
+ uint8_t pk[(pk_bytes)]; \
305
+ int rc; \
306
+ if (!sk) { \
307
+ return PQ_ERROR_BUFFER; \
308
+ } \
309
+ rc = native##_pk_from_sk(pk, sk); \
310
+ pq_secure_wipe(pk, sizeof(pk)); \
311
+ if (rc == 0) { \
312
+ return PQ_SUCCESS; \
313
+ } \
314
+ if (rc == -2) { \
315
+ return PQ_ERROR_NOMEM; \
316
+ } \
317
+ return PQ_ERROR_INVALID_SECRET_KEY; \
318
+ }
319
+
320
+ PQ_DEFINE_MLDSA_CHECK_SK(mldsa, pqcr_mldsa65, MLDSA65_PUBLICKEYBYTES)
321
+ PQ_DEFINE_MLDSA_CHECK_SK(mldsa44, pqcr_mldsa44, MLDSA44_PUBLICKEYBYTES)
322
+ PQ_DEFINE_MLDSA_CHECK_SK(mldsa87, pqcr_mldsa87, MLDSA87_PUBLICKEYBYTES)
323
+
324
+ #undef PQ_DEFINE_MLDSA_CHECK_SK
325
+
274
326
  static int pq_testing_mlkem_keypair_from_seed_with(uint8_t *public_key, uint8_t *secret_key,
275
327
  const uint8_t *seed, size_t seed_len,
276
328
  int (*keypair_derand)(uint8_t *, uint8_t *,
@@ -29,7 +29,9 @@ typedef enum {
29
29
  PQ_ERROR_RANDOM = -7,
30
30
  PQ_ERROR_BUFFER = -8,
31
31
  PQ_ERROR_NOMEM = -9,
32
- PQ_ERROR_OPENSSL = -10
32
+ PQ_ERROR_OPENSSL = -10,
33
+ PQ_ERROR_INVALID_PUBLIC_KEY = -11,
34
+ PQ_ERROR_INVALID_SECRET_KEY = -12
33
35
  } pq_error_t;
34
36
 
35
37
  typedef struct {
@@ -83,6 +85,17 @@ int pq_mlkem512_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
83
85
  int pq_mlkem1024_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
84
86
  const uint8_t *secret_key);
85
87
 
88
+ int pq_mlkem_check_public_key(const uint8_t *public_key);
89
+ int pq_mlkem_check_secret_key(const uint8_t *secret_key);
90
+ int pq_mlkem512_check_public_key(const uint8_t *public_key);
91
+ int pq_mlkem512_check_secret_key(const uint8_t *secret_key);
92
+ int pq_mlkem1024_check_public_key(const uint8_t *public_key);
93
+ int pq_mlkem1024_check_secret_key(const uint8_t *secret_key);
94
+
95
+ int pq_mldsa_check_secret_key(const uint8_t *secret_key);
96
+ int pq_mldsa44_check_secret_key(const uint8_t *secret_key);
97
+ int pq_mldsa87_check_secret_key(const uint8_t *secret_key);
98
+
86
99
  int pq_sign_keypair(uint8_t *public_key, uint8_t *secret_key);
87
100
  int pq_mldsa44_sign_keypair(uint8_t *public_key, uint8_t *secret_key);
88
101
  int pq_mldsa87_sign_keypair(uint8_t *public_key, uint8_t *secret_key);
@@ -2,6 +2,6 @@
2
2
  #ifndef PQCRYPTO_VERSION_H
3
3
  #define PQCRYPTO_VERSION_H
4
4
 
5
- #define PQCRYPTO_VERSION "0.6.6"
5
+ #define PQCRYPTO_VERSION "0.6.7"
6
6
 
7
7
  #endif
@@ -8,6 +8,16 @@ module PQCrypto
8
8
  String(value).b
9
9
  end
10
10
 
11
+ def frozen_binary_string(value)
12
+ binary_string(value).freeze
13
+ end
14
+
15
+ def strict_boolean!(value, name:)
16
+ return value if value.equal?(true) || value.equal?(false)
17
+
18
+ raise ArgumentError, "#{name} must be true or false (got #{value.inspect})"
19
+ end
20
+
11
21
  def safe_wipe(value)
12
22
  return unless value.is_a?(String) && !value.frozen?
13
23
 
data/lib/pq_crypto/kem.rb CHANGED
@@ -14,18 +14,24 @@ module PQCrypto
14
14
  keypair_from_seed: :native_ml_kem_512_keypair_from_seed,
15
15
  encapsulate: :native_ml_kem_512_encapsulate,
16
16
  decapsulate: :native_ml_kem_512_decapsulate,
17
+ check_public_key: :native_ml_kem_512_check_public_key,
18
+ check_secret_key: :native_ml_kem_512_check_secret_key,
17
19
  }.freeze,
18
20
  ml_kem_768: {
19
21
  keypair: :native_ml_kem_keypair,
20
22
  keypair_from_seed: :native_ml_kem_keypair_from_seed,
21
23
  encapsulate: :native_ml_kem_encapsulate,
22
24
  decapsulate: :native_ml_kem_decapsulate,
25
+ check_public_key: :native_ml_kem_check_public_key,
26
+ check_secret_key: :native_ml_kem_check_secret_key,
23
27
  }.freeze,
24
28
  ml_kem_1024: {
25
29
  keypair: :native_ml_kem_1024_keypair,
26
30
  keypair_from_seed: :native_ml_kem_1024_keypair_from_seed,
27
31
  encapsulate: :native_ml_kem_1024_encapsulate,
28
32
  decapsulate: :native_ml_kem_1024_decapsulate,
33
+ check_public_key: :native_ml_kem_1024_check_public_key,
34
+ check_secret_key: :native_ml_kem_1024_check_secret_key,
29
35
  }.freeze,
30
36
  }.freeze
31
37
 
@@ -68,12 +74,18 @@ module PQCrypto
68
74
  SecretKey.new(resolve_algorithm!(resolved_algorithm), bytes)
69
75
  end
70
76
 
71
- def secret_key_from_pkcs8_der(der, passphrase: nil)
72
- secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der, passphrase: passphrase))
77
+ def secret_key_from_pkcs8_der(der, passphrase: nil, require_encrypted: false)
78
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
79
+ secret_key_from_decoded_pkcs8(
80
+ *PKCS8.decode_der(der, passphrase: passphrase, require_encrypted: require_encrypted)
81
+ )
73
82
  end
74
83
 
75
- def secret_key_from_pkcs8_pem(pem, passphrase: nil)
76
- secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem, passphrase: passphrase))
84
+ def secret_key_from_pkcs8_pem(pem, passphrase: nil, require_encrypted: false)
85
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
86
+ secret_key_from_decoded_pkcs8(
87
+ *PKCS8.decode_pem(pem, passphrase: passphrase, require_encrypted: require_encrypted)
88
+ )
77
89
  end
78
90
 
79
91
  def public_key_from_spki_der(der, algorithm: nil)
@@ -121,6 +133,10 @@ module PQCrypto
121
133
  end
122
134
  end
123
135
 
136
+ def checkable?(algorithm)
137
+ NATIVE_DISPATCH.key?(algorithm) && NATIVE_DISPATCH.fetch(algorithm).key?(:check_public_key)
138
+ end
139
+
124
140
  def native_method_for(algorithm, operation)
125
141
  NATIVE_DISPATCH.fetch(resolve_algorithm!(algorithm)).fetch(operation)
126
142
  end
@@ -158,8 +174,19 @@ module PQCrypto
158
174
 
159
175
  def initialize(algorithm, bytes)
160
176
  @algorithm = algorithm
161
- @bytes = Internal.binary_string(bytes)
177
+ @bytes = Internal.frozen_binary_string(bytes)
162
178
  validate_length!
179
+ validate_structure!
180
+ end
181
+
182
+ def valid?
183
+ if @algorithm == :ml_kem_768_x25519_xwing
184
+ mlkem_half = @bytes.byteslice(0, KEM.details(:ml_kem_768).fetch(:public_key_bytes))
185
+ return PQCrypto.__send__(:native_ml_kem_check_public_key, mlkem_half)
186
+ end
187
+ return true unless KEM.send(:checkable?, @algorithm)
188
+
189
+ PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :check_public_key), @bytes)
163
190
  end
164
191
 
165
192
  def to_bytes
@@ -219,6 +246,10 @@ module PQCrypto
219
246
  expected = KEM.details(@algorithm).fetch(:public_key_bytes)
220
247
  raise InvalidKeyError, "Invalid KEM public key length" unless @bytes.bytesize == expected
221
248
  end
249
+
250
+ def validate_structure!
251
+ raise InvalidKeyError, "Invalid #{@algorithm} public key: FIPS 203 modulus check failed" unless valid?
252
+ end
222
253
  end
223
254
 
224
255
  class SecretKey
@@ -230,6 +261,13 @@ module PQCrypto
230
261
  @seed = seed.nil? ? nil : Internal.binary_string(seed)
231
262
  validate_length!
232
263
  validate_seed_length! if @seed
264
+ validate_structure!
265
+ end
266
+
267
+ def valid?
268
+ return true unless KEM.send(:checkable?, @algorithm)
269
+
270
+ PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :check_secret_key), @bytes)
233
271
  end
234
272
 
235
273
  def self.from_seed(algorithm, seed)
@@ -298,6 +336,10 @@ module PQCrypto
298
336
  raise InvalidKeyError, "Invalid KEM secret key length" unless @bytes.bytesize == expected
299
337
  end
300
338
 
339
+ def validate_structure!
340
+ raise InvalidKeyError, "Invalid #{@algorithm} secret key: FIPS 203 hash check failed" unless valid?
341
+ end
342
+
301
343
  def pkcs8_material(format)
302
344
  case format
303
345
  when :expanded
data/lib/pq_crypto/key.rb CHANGED
@@ -17,22 +17,24 @@ module PQCrypto
17
17
  end
18
18
  end
19
19
 
20
- def from_pem(pem, passphrase: nil)
20
+ def from_pem(pem, passphrase: nil, require_encrypted: false)
21
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
21
22
  text = String(pem)
22
23
  if text.include?(SPKI::PEM_BEGIN)
23
24
  public_key_from_spki_pem(text)
24
25
  elsif text.include?(PKCS8::PEM_BEGIN) || text.include?(PKCS8::ENCRYPTED_PEM_BEGIN)
25
- secret_key_from_pkcs8_pem(text, passphrase: passphrase)
26
+ secret_key_from_pkcs8_pem(text, passphrase: passphrase, require_encrypted: require_encrypted)
26
27
  else
27
28
  raise SerializationError, "Unsupported PEM label for PQCrypto::Key.from_pem"
28
29
  end
29
30
  end
30
31
 
31
- def from_der(der, passphrase: nil)
32
+ def from_der(der, passphrase: nil, require_encrypted: false)
33
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
32
34
  public_key_from_spki_der(der)
33
35
  rescue SerializationError => spki_error
34
36
  begin
35
- secret_key_from_pkcs8_der(der, passphrase: passphrase)
37
+ secret_key_from_pkcs8_der(der, passphrase: passphrase, require_encrypted: require_encrypted)
36
38
  rescue SerializationError => pkcs8_error
37
39
  raise SerializationError,
38
40
  "Unable to decode DER as SPKI or PKCS#8 (SPKI: #{spki_error.message}; PKCS#8: #{pkcs8_error.message})"
@@ -56,12 +58,16 @@ module PQCrypto
56
58
  public_key_from_algorithm_and_bytes(algorithm, bytes)
57
59
  end
58
60
 
59
- def secret_key_from_pkcs8_pem(pem, passphrase: nil)
60
- secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem, passphrase: passphrase))
61
+ def secret_key_from_pkcs8_pem(pem, passphrase: nil, require_encrypted: false)
62
+ secret_key_from_decoded_pkcs8(
63
+ *PKCS8.decode_pem(pem, passphrase: passphrase, require_encrypted: require_encrypted)
64
+ )
61
65
  end
62
66
 
63
- def secret_key_from_pkcs8_der(der, passphrase: nil)
64
- secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der, passphrase: passphrase))
67
+ def secret_key_from_pkcs8_der(der, passphrase: nil, require_encrypted: false)
68
+ secret_key_from_decoded_pkcs8(
69
+ *PKCS8.decode_der(der, passphrase: passphrase, require_encrypted: require_encrypted)
70
+ )
65
71
  end
66
72
 
67
73
  def secret_key_from_decoded_pkcs8(algorithm, format, material)
@@ -70,10 +70,16 @@ module PQCrypto
70
70
  der = encode_der(algorithm, secret_material, format: format, passphrase: passphrase, iterations: iterations)
71
71
  pkcs8_native { PQCrypto.__send__(:native_pkcs8_der_to_pem, der, !passphrase.nil?) }
72
72
  end
73
-
74
- def decode_der(der, passphrase: nil)
73
+ def decode_der(der, passphrase: nil, require_encrypted: false)
74
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
75
75
  input = Internal.binary_string(der)
76
- return decode_encrypted_der(input, passphrase: passphrase) if encrypted_der?(input)
76
+ encrypted = encrypted_der?(input)
77
+
78
+ if require_encrypted && !encrypted
79
+ raise SerializationError, "PKCS#8 input is not encrypted, but require_encrypted: true was requested"
80
+ end
81
+
82
+ return decode_encrypted_der(input, passphrase: passphrase) if encrypted
77
83
 
78
84
  oid, choice_der = private_key_info_from_der(input)
79
85
  algorithm = AlgorithmRegistry.by_standard_oid(oid)
@@ -83,15 +89,17 @@ module PQCrypto
83
89
  PrivateKeyChoice.validate_secret_key_algorithm!(algorithm, entry)
84
90
  PrivateKeyChoice.decode(algorithm, Internal.binary_string(choice_der))
85
91
  rescue ArgumentError => e
92
+ raise if e.message.start_with?("require_encrypted")
86
93
  raise SerializationError, e.message
87
94
  ensure
88
95
  Internal.safe_wipe(choice_der) if defined?(choice_der)
89
96
  end
90
97
 
91
- def decode_pem(pem, passphrase: nil)
98
+ def decode_pem(pem, passphrase: nil, require_encrypted: false)
99
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
92
100
  _encrypted, der = pkcs8_native { PQCrypto.__send__(:native_pkcs8_pem_to_der, String(pem)) }
93
101
  begin
94
- decode_der(der, passphrase: passphrase)
102
+ decode_der(der, passphrase: passphrase, require_encrypted: require_encrypted)
95
103
  ensure
96
104
  Internal.safe_wipe(der)
97
105
  end
@@ -14,18 +14,21 @@ module PQCrypto
14
14
  sign: :native_ml_dsa_44_sign,
15
15
  verify: :native_ml_dsa_44_verify,
16
16
  keypair_from_seed: :native_ml_dsa_44_keypair_from_seed,
17
+ check_secret_key: :native_ml_dsa_44_check_secret_key,
17
18
  }.freeze,
18
19
  ml_dsa_65: {
19
20
  keypair: :native_sign_keypair,
20
21
  sign: :native_sign,
21
22
  verify: :native_verify,
22
23
  keypair_from_seed: :native_ml_dsa_keypair_from_seed,
24
+ check_secret_key: :native_ml_dsa_check_secret_key,
23
25
  }.freeze,
24
26
  ml_dsa_87: {
25
27
  keypair: :native_ml_dsa_87_keypair,
26
28
  sign: :native_ml_dsa_87_sign,
27
29
  verify: :native_ml_dsa_87_verify,
28
30
  keypair_from_seed: :native_ml_dsa_87_keypair_from_seed,
31
+ check_secret_key: :native_ml_dsa_87_check_secret_key,
29
32
  }.freeze,
30
33
  }.freeze
31
34
 
@@ -33,7 +36,8 @@ module PQCrypto
33
36
  def generate(algorithm = CANONICAL_ALGORITHM)
34
37
  algorithm = resolve_algorithm!(algorithm)
35
38
  public_key, secret_key = PQCrypto.__send__(native_method_for(algorithm, :keypair))
36
- Keypair.new(PublicKey.new(algorithm, public_key), SecretKey.new(algorithm, secret_key))
39
+ Keypair.new(PublicKey.new(algorithm, public_key),
40
+ SecretKey.new(algorithm, secret_key, freshly_generated: true))
37
41
  end
38
42
 
39
43
  def public_key_from_bytes(algorithm, bytes)
@@ -86,12 +90,18 @@ module PQCrypto
86
90
  PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
87
91
  end
88
92
 
89
- def secret_key_from_pkcs8_der(der, passphrase: nil)
90
- secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der, passphrase: passphrase))
93
+ def secret_key_from_pkcs8_der(der, passphrase: nil, require_encrypted: false)
94
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
95
+ secret_key_from_decoded_pkcs8(
96
+ *PKCS8.decode_der(der, passphrase: passphrase, require_encrypted: require_encrypted)
97
+ )
91
98
  end
92
99
 
93
- def secret_key_from_pkcs8_pem(pem, passphrase: nil)
94
- secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem, passphrase: passphrase))
100
+ def secret_key_from_pkcs8_pem(pem, passphrase: nil, require_encrypted: false)
101
+ require_encrypted = Internal.strict_boolean!(require_encrypted, name: "require_encrypted")
102
+ secret_key_from_decoded_pkcs8(
103
+ *PKCS8.decode_pem(pem, passphrase: passphrase, require_encrypted: require_encrypted)
104
+ )
95
105
  end
96
106
 
97
107
  def details(algorithm)
@@ -118,7 +128,7 @@ module PQCrypto
118
128
  SecretKey.new(algorithm, material)
119
129
  when :seed
120
130
  _public_key, expanded = PQCrypto.__send__(native_method_for(algorithm, :keypair_from_seed), material)
121
- SecretKey.new(algorithm, expanded, seed: material)
131
+ SecretKey.new(algorithm, expanded, seed: material, freshly_generated: true)
122
132
  when :both
123
133
  _seed, expanded = material
124
134
  SecretKey.new(algorithm, expanded, seed: _seed)
@@ -133,6 +143,10 @@ module PQCrypto
133
143
  NATIVE_DISPATCH.fetch(resolve_algorithm!(algorithm)).fetch(operation)
134
144
  end
135
145
 
146
+ def secret_key_checkable?(algorithm)
147
+ NATIVE_DISPATCH.key?(algorithm) && NATIVE_DISPATCH.fetch(algorithm).key?(:check_secret_key)
148
+ end
149
+
136
150
  def validate_algorithm_match!(expected_algorithm, actual_algorithm)
137
151
  expected = resolve_algorithm!(expected_algorithm)
138
152
  return if expected == actual_algorithm
@@ -264,7 +278,7 @@ module PQCrypto
264
278
 
265
279
  def initialize(algorithm, bytes)
266
280
  @algorithm = algorithm
267
- @bytes = Internal.binary_string(bytes)
281
+ @bytes = Internal.frozen_binary_string(bytes)
268
282
  validate_length!
269
283
  end
270
284
 
@@ -343,18 +357,25 @@ module PQCrypto
343
357
  class SecretKey
344
358
  attr_reader :algorithm
345
359
 
346
- def initialize(algorithm, bytes, seed: nil)
360
+ def initialize(algorithm, bytes, seed: nil, freshly_generated: false)
347
361
  @algorithm = algorithm
348
362
  @bytes = Internal.binary_string(bytes)
349
363
  @seed = seed.nil? ? nil : Internal.binary_string(seed)
350
364
  validate_length!
351
365
  validate_seed_length! if @seed
366
+ validate_structure! unless freshly_generated
367
+ end
368
+
369
+ def valid?
370
+ return true unless Signature.send(:secret_key_checkable?, @algorithm)
371
+
372
+ PQCrypto.__send__(Signature.send(:native_method_for, @algorithm, :check_secret_key), @bytes)
352
373
  end
353
374
 
354
375
  def self.from_seed(algorithm, seed)
355
376
  seed_bytes = Internal.binary_string(seed)
356
377
  _public_key, expanded = PQCrypto.__send__(Signature.send(:native_method_for, algorithm, :keypair_from_seed), seed_bytes)
357
- new(algorithm, expanded, seed: seed_bytes)
378
+ new(algorithm, expanded, seed: seed_bytes, freshly_generated: true)
358
379
  rescue ArgumentError => e
359
380
  raise InvalidKeyError, e.message
360
381
  end
@@ -424,6 +445,10 @@ module PQCrypto
424
445
  raise InvalidKeyError, "Invalid signature secret key length" unless @bytes.bytesize == expected
425
446
  end
426
447
 
448
+ def validate_structure!
449
+ raise InvalidKeyError, "Invalid #{@algorithm} secret key: ML-DSA structure check failed" unless valid?
450
+ end
451
+
427
452
  def pkcs8_material(format)
428
453
  case format
429
454
  when :expanded
@@ -26,7 +26,7 @@ module PQCrypto
26
26
  ]),
27
27
  OpenSSL::ASN1::BitString.new(bytes),
28
28
  ]).to_der.b
29
- rescue OpenSSL::ASN1::ASN1Error => e
29
+ rescue OpenSSL::OpenSSLError, TypeError => e
30
30
  raise SerializationError, e.message
31
31
  end
32
32
 
@@ -63,6 +63,8 @@ module PQCrypto
63
63
  end
64
64
 
65
65
  [algorithm, bytes]
66
+ rescue OpenSSL::OpenSSLError, TypeError => e
67
+ raise SerializationError, e.message
66
68
  end
67
69
 
68
70
  def decode_pem(pem)
@@ -74,7 +76,7 @@ module PQCrypto
74
76
 
75
77
  def decode_asn1(der)
76
78
  OpenSSL::ASN1.decode(der)
77
- rescue OpenSSL::ASN1::ASN1Error => e
79
+ rescue OpenSSL::OpenSSLError, TypeError => e
78
80
  raise SerializationError, e.message
79
81
  end
80
82
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PQCrypto
4
- VERSION = "0.6.6"
4
+ VERSION = "0.6.7"
5
5
  end
data/lib/pq_crypto.rb CHANGED
@@ -65,6 +65,15 @@ module PQCrypto
65
65
  ml_kem_1024_keypair_from_seed
66
66
  ml_kem_1024_encapsulate
67
67
  ml_kem_1024_decapsulate
68
+ ml_kem_check_public_key
69
+ ml_kem_check_secret_key
70
+ ml_kem_512_check_public_key
71
+ ml_kem_512_check_secret_key
72
+ ml_kem_1024_check_public_key
73
+ ml_kem_1024_check_secret_key
74
+ ml_dsa_check_secret_key
75
+ ml_dsa_44_check_secret_key
76
+ ml_dsa_87_check_secret_key
68
77
  hybrid_kem_keypair
69
78
  hybrid_kem_encapsulate
70
79
  hybrid_kem_expand_secret_key
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pq_crypto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.6
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov