pq_crypto 0.1.0 → 0.2.0

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: dcc40bb87fcd74ce1d16bb32d99df894cb127e0c77c879cba7ec7d6ef8e099e8
4
- data.tar.gz: 6baf54010794cca2d1a9f81f884f1a6db16355515f83ed1812baa57feeaf63dc
3
+ metadata.gz: a9f15d910879cdfa645c90795ee80932a0261322401f42e05a0623ce938cfce7
4
+ data.tar.gz: 126bd8e2435fedfe9aa46b046bf98215e972f9fc81d5856414015357afee6bbf
5
5
  SHA512:
6
- metadata.gz: feb14e5bc0aefc8fa3b9b3d24083cd93923466d9bc1c5a1e4e527325f0290124be785da63aca97e342c9885e1e56b92e2637007a93c47d85ac950bee2317af71
7
- data.tar.gz: 1a7773c9850dfea54df3897a6ee092f99258e25b0fa5393c977fa7e70c3eed309bca54044711501a6475f0d30ca56753eecaa768e87c9620a4e581b015c3332a
6
+ metadata.gz: 154020cb51f31d414e83c09cf9a7cc410fc8395500a97777fb741ff77986f4e5ae2fda7e64e932eaf17eb9d653901a63f114d5710a672aa5f954e756c2509898
7
+ data.tar.gz: c2ea78f4b001a32d486f876832832248261bf42d80ddbbe5821faed59240d6d55c80e67ed486c4476a1e47ae5fd1d46cbf64fc0903f2483ff8c3cf34c15a2388
@@ -13,7 +13,7 @@ jobs:
13
13
  fail-fast: false
14
14
  matrix:
15
15
  os: [ubuntu-latest, macos-latest]
16
- ruby: ["3.1", "3.2", "3.3", "3.4"]
16
+ ruby: ["3.4", "4.0"]
17
17
 
18
18
  steps:
19
19
  - name: Checkout
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0]
4
+
5
+ ### Changed
6
+
7
+ - Raised the minimum supported Ruby to the 3.4 series.
8
+ - Switched `PQCrypto::Signature::SecretKey#sign` and `PQCrypto::Signature::PublicKey#verify` to Ruby 3.4's scheduler-aware `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` path.
9
+ - Left the faster KEM and key-generation operations on the existing lower-overhead no-GVL path.
10
+ - Removed gem-specific scheduler configuration; runtime behavior now follows the active Ruby Fiber scheduler automatically.
11
+
12
+ ### Testing
13
+
14
+ - Added Async integration tests that verify sibling `task.async` work keeps making progress while `sign` and `verify` run under an Async worker-pool-enabled reactor.
15
+ - Updated CI to target the supported Ruby 3.4 series.
16
+
3
17
  ## [0.1.0]
4
18
 
5
19
  Initial public release.
data/README.md CHANGED
@@ -35,10 +35,48 @@ bundle exec rake compile
35
35
 
36
36
  ### Native dependencies
37
37
 
38
- - Ruby 3.1+
38
+ - Ruby 3.4.x
39
39
  - a C toolchain
40
40
  - OpenSSL **3.0 or later**
41
41
 
42
+ ## Async / Fiber scheduler support
43
+
44
+ `pq_crypto` does not require any gem-specific Async configuration. On Ruby 3.4, `sign` and `verify` use Ruby's scheduler-aware `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` path automatically.
45
+
46
+ That means:
47
+
48
+ - without a Fiber scheduler, these methods fall back to the ordinary no-GVL behavior;
49
+ - with a scheduler that implements `blocking_operation_wait` (for example `Async` with a worker pool), the blocking native work can be moved off the event loop.
50
+
51
+ This integration is intentionally limited to `sign` and `verify`; the faster primitive operations keep the lower-overhead path.
52
+
53
+ Example with `Async`:
54
+
55
+ ```ruby
56
+ require "async"
57
+ require "pq_crypto"
58
+
59
+ keypair = PQCrypto::Signature.generate(:ml_dsa_65)
60
+ message = "hello" * 100_000
61
+
62
+ reactor = Async::Reactor.new(worker_pool: true)
63
+ root = reactor.async do |task|
64
+ task.async do
65
+ signature = keypair.secret_key.sign(message)
66
+ keypair.public_key.verify(message, signature)
67
+ end
68
+
69
+ task.async do
70
+ sleep 0.01
71
+ puts "event loop stayed responsive"
72
+ end
73
+ end
74
+
75
+ reactor.run
76
+ root.wait
77
+ reactor.close
78
+ ```
79
+
42
80
  ## Primitive API
43
81
 
44
82
  ### ML-KEM-768
@@ -62,6 +62,9 @@ static VALUE mPQCrypto;
62
62
  static VALUE ePQCryptoError;
63
63
  static VALUE ePQCryptoVerificationError;
64
64
 
65
+ __attribute__((noreturn)) static void pq_raise_general_error(int err);
66
+ __attribute__((noreturn)) static void pq_raise_verification_error(int err);
67
+
65
68
  static const char *pq_algorithm_symbol_to_cstr(VALUE algorithm) {
66
69
  ID id;
67
70
  if (SYMBOL_P(algorithm)) {
@@ -215,230 +218,294 @@ static void pq_wipe_and_free(uint8_t *buffer, size_t len) {
215
218
  }
216
219
  }
217
220
 
218
- __attribute__((noreturn)) static void pq_raise_general_error(int err) {
219
- switch (err) {
220
- case PQ_ERROR_KEYPAIR:
221
- rb_raise(ePQCryptoError, "Keypair generation failed");
222
- break;
223
- case PQ_ERROR_ENCAPSULATE:
224
- rb_raise(ePQCryptoError, "Encapsulation failed");
225
- break;
226
- case PQ_ERROR_DECAPSULATE:
227
- rb_raise(ePQCryptoError, "Decapsulation failed");
228
- break;
229
- case PQ_ERROR_SIGN:
230
- rb_raise(ePQCryptoError, "Signing failed");
231
- break;
232
- case PQ_ERROR_VERIFY:
233
- rb_raise(ePQCryptoError, "Verification failed");
234
- break;
235
- case PQ_ERROR_RANDOM:
236
- rb_raise(ePQCryptoError, "Random number generation failed");
237
- break;
238
- case PQ_ERROR_KDF:
239
- rb_raise(ePQCryptoError, "Key derivation failed");
240
- break;
241
- case PQ_ERROR_BUFFER:
242
- rb_raise(ePQCryptoError, "Buffer error");
243
- break;
244
- case PQ_ERROR_NOMEM:
245
- rb_raise(rb_eNoMemError, "Memory allocation failed");
246
- break;
247
- case PQ_ERROR_OPENSSL:
248
- rb_raise(ePQCryptoError, "OpenSSL error");
249
- break;
250
- default:
251
- rb_raise(ePQCryptoError, "Unknown error: %d", err);
252
- break;
221
+ static void pq_validate_bytes_argument(VALUE value, size_t expected_len, const char *what) {
222
+ StringValue(value);
223
+ if ((size_t)RSTRING_LEN(value) != expected_len) {
224
+ rb_raise(rb_eArgError, "Invalid %s length", what);
253
225
  }
254
226
  }
255
227
 
256
- __attribute__((noreturn)) static void pq_raise_verification_error(int err) {
257
- switch (err) {
258
- case PQ_ERROR_VERIFY:
259
- rb_raise(ePQCryptoVerificationError, "Verification failed");
260
- break;
261
- default:
262
- pq_raise_general_error(err);
263
- }
228
+ static VALUE pq_build_binary_pair(const uint8_t *first, size_t first_len, const uint8_t *second,
229
+ size_t second_len) {
230
+ VALUE result = rb_ary_new2(2);
231
+ rb_ary_push(result, pq_string_from_buffer(first, first_len));
232
+ rb_ary_push(result, pq_string_from_buffer(second, second_len));
233
+ return result;
264
234
  }
265
235
 
266
- static VALUE pqcrypto_ml_kem_keypair(VALUE self) {
267
- (void)self;
236
+ static VALUE pq_build_algorithm_key_pair(const char *algorithm, const uint8_t *key,
237
+ size_t key_len) {
238
+ VALUE result = rb_ary_new2(2);
239
+ rb_ary_push(result, pq_algorithm_cstr_to_symbol(algorithm));
240
+ rb_ary_push(result, pq_string_from_buffer(key, key_len));
241
+ return result;
242
+ }
268
243
 
244
+ static VALUE pq_run_kem_keypair(void *(*nogvl)(void *), size_t public_key_len,
245
+ size_t secret_key_len) {
269
246
  kem_keypair_call_t call = {0};
270
- call.public_key = pq_alloc_buffer(PQ_MLKEM_PUBLICKEYBYTES);
271
- call.secret_key = pq_alloc_buffer(PQ_MLKEM_SECRETKEYBYTES);
247
+ VALUE result;
272
248
 
273
- rb_thread_call_without_gvl(pq_ml_kem_keypair_nogvl, &call, NULL, NULL);
249
+ call.public_key = pq_alloc_buffer(public_key_len);
250
+ call.secret_key = pq_alloc_buffer(secret_key_len);
251
+
252
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
274
253
 
275
254
  if (call.result != PQ_SUCCESS) {
276
- pq_wipe_and_free(call.secret_key, PQ_MLKEM_SECRETKEYBYTES);
255
+ pq_wipe_and_free(call.secret_key, secret_key_len);
277
256
  free(call.public_key);
278
257
  pq_raise_general_error(call.result);
279
258
  }
280
259
 
281
- VALUE result = rb_ary_new2(2);
282
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_MLKEM_PUBLICKEYBYTES));
283
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_MLKEM_SECRETKEYBYTES));
284
-
260
+ result = pq_build_binary_pair(call.public_key, public_key_len, call.secret_key, secret_key_len);
285
261
  free(call.public_key);
286
- pq_wipe_and_free(call.secret_key, PQ_MLKEM_SECRETKEYBYTES);
262
+ pq_wipe_and_free(call.secret_key, secret_key_len);
287
263
  return result;
288
264
  }
289
265
 
290
- static VALUE pqcrypto_ml_kem_encapsulate(VALUE self, VALUE public_key) {
291
- (void)self;
292
- StringValue(public_key);
266
+ static VALUE pq_run_kem_encapsulate(void *(*nogvl)(void *), VALUE public_key, size_t public_key_len,
267
+ size_t ciphertext_len, size_t shared_secret_len) {
268
+ kem_encapsulate_call_t call = {0};
269
+ VALUE result;
270
+ size_t copied_public_key_len = 0;
293
271
 
294
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLKEM_PUBLICKEYBYTES) {
295
- rb_raise(rb_eArgError, "Invalid public key length");
296
- }
272
+ pq_validate_bytes_argument(public_key, public_key_len, "public key");
297
273
 
298
- kem_encapsulate_call_t call = {0};
299
- size_t public_key_len = 0;
300
- call.public_key = pq_copy_ruby_string(public_key, &public_key_len);
301
- call.ciphertext = pq_alloc_buffer(PQ_MLKEM_CIPHERTEXTBYTES);
302
- call.shared_secret = pq_alloc_buffer(PQ_MLKEM_SHAREDSECRETBYTES);
274
+ call.public_key = pq_copy_ruby_string(public_key, &copied_public_key_len);
275
+ call.ciphertext = pq_alloc_buffer(ciphertext_len);
276
+ call.shared_secret = pq_alloc_buffer(shared_secret_len);
303
277
 
304
- rb_thread_call_without_gvl(pq_ml_kem_encapsulate_nogvl, &call, NULL, NULL);
305
- pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
278
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
279
+ pq_wipe_and_free((uint8_t *)call.public_key, copied_public_key_len);
306
280
 
307
281
  if (call.result != PQ_SUCCESS) {
308
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
282
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
309
283
  free(call.ciphertext);
310
284
  pq_raise_general_error(call.result);
311
285
  }
312
286
 
313
- VALUE result = rb_ary_new2(2);
314
- rb_ary_push(result, pq_string_from_buffer(call.ciphertext, PQ_MLKEM_CIPHERTEXTBYTES));
315
- rb_ary_push(result, pq_string_from_buffer(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES));
316
-
287
+ result = pq_build_binary_pair(call.ciphertext, ciphertext_len, call.shared_secret,
288
+ shared_secret_len);
317
289
  free(call.ciphertext);
318
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
290
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
319
291
  return result;
320
292
  }
321
293
 
322
- static VALUE pqcrypto_ml_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
323
- (void)self;
324
- StringValue(ciphertext);
325
- StringValue(secret_key);
294
+ static VALUE pq_run_kem_decapsulate(void *(*nogvl)(void *), VALUE ciphertext, size_t ciphertext_len,
295
+ VALUE secret_key, size_t secret_key_len,
296
+ size_t shared_secret_len) {
297
+ kem_decapsulate_call_t call = {0};
298
+ VALUE result;
299
+ size_t copied_ciphertext_len = 0;
300
+ size_t copied_secret_key_len = 0;
326
301
 
327
- if ((size_t)RSTRING_LEN(ciphertext) != PQ_MLKEM_CIPHERTEXTBYTES) {
328
- rb_raise(rb_eArgError, "Invalid ciphertext length");
329
- }
330
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLKEM_SECRETKEYBYTES) {
331
- rb_raise(rb_eArgError, "Invalid secret key length");
332
- }
302
+ pq_validate_bytes_argument(ciphertext, ciphertext_len, "ciphertext");
303
+ pq_validate_bytes_argument(secret_key, secret_key_len, "secret key");
333
304
 
334
- kem_decapsulate_call_t call = {0};
335
- size_t ciphertext_len = 0;
336
- size_t secret_key_len = 0;
337
- call.ciphertext = pq_copy_ruby_string(ciphertext, &ciphertext_len);
338
- call.secret_key = pq_copy_ruby_string(secret_key, &secret_key_len);
339
- call.shared_secret = pq_alloc_buffer(PQ_MLKEM_SHAREDSECRETBYTES);
305
+ call.ciphertext = pq_copy_ruby_string(ciphertext, &copied_ciphertext_len);
306
+ call.secret_key = pq_copy_ruby_string(secret_key, &copied_secret_key_len);
307
+ call.shared_secret = pq_alloc_buffer(shared_secret_len);
340
308
 
341
- rb_thread_call_without_gvl(pq_ml_kem_decapsulate_nogvl, &call, NULL, NULL);
342
- pq_wipe_and_free((uint8_t *)call.ciphertext, ciphertext_len);
343
- pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
309
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
310
+ pq_wipe_and_free((uint8_t *)call.ciphertext, copied_ciphertext_len);
311
+ pq_wipe_and_free((uint8_t *)call.secret_key, copied_secret_key_len);
344
312
 
345
313
  if (call.result != PQ_SUCCESS) {
346
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
314
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
347
315
  pq_raise_general_error(call.result);
348
316
  }
349
317
 
350
- VALUE result = pq_string_from_buffer(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
351
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
318
+ result = pq_string_from_buffer(call.shared_secret, shared_secret_len);
319
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
352
320
  return result;
353
321
  }
354
322
 
355
- static VALUE pqcrypto_hybrid_kem_keypair(VALUE self) {
356
- (void)self;
323
+ static VALUE pq_run_sign_keypair(void *(*nogvl)(void *), size_t public_key_len,
324
+ size_t secret_key_len) {
325
+ sign_keypair_call_t call = {0};
326
+ VALUE result;
357
327
 
358
- kem_keypair_call_t call = {0};
359
- call.public_key = pq_alloc_buffer(PQ_HYBRID_PUBLICKEYBYTES);
360
- call.secret_key = pq_alloc_buffer(PQ_HYBRID_SECRETKEYBYTES);
328
+ call.public_key = pq_alloc_buffer(public_key_len);
329
+ call.secret_key = pq_alloc_buffer(secret_key_len);
361
330
 
362
- rb_thread_call_without_gvl(pq_hybrid_kem_keypair_nogvl, &call, NULL, NULL);
331
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
363
332
 
364
333
  if (call.result != PQ_SUCCESS) {
365
- pq_wipe_and_free(call.secret_key, PQ_HYBRID_SECRETKEYBYTES);
334
+ pq_wipe_and_free(call.secret_key, secret_key_len);
366
335
  free(call.public_key);
367
336
  pq_raise_general_error(call.result);
368
337
  }
369
338
 
370
- VALUE result = rb_ary_new2(2);
371
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_HYBRID_PUBLICKEYBYTES));
372
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_HYBRID_SECRETKEYBYTES));
373
-
339
+ result = pq_build_binary_pair(call.public_key, public_key_len, call.secret_key, secret_key_len);
374
340
  free(call.public_key);
375
- pq_wipe_and_free(call.secret_key, PQ_HYBRID_SECRETKEYBYTES);
341
+ pq_wipe_and_free(call.secret_key, secret_key_len);
376
342
  return result;
377
343
  }
378
344
 
379
- static VALUE pqcrypto_hybrid_kem_encapsulate(VALUE self, VALUE public_key) {
380
- (void)self;
381
- StringValue(public_key);
345
+ typedef int (*pq_export_der_fn)(uint8_t **, size_t *, const uint8_t *, size_t, const char *);
346
+ typedef int (*pq_export_pem_fn)(char **, size_t *, const uint8_t *, size_t, const char *);
347
+ typedef int (*pq_import_der_fn)(char **, uint8_t **, size_t *, const uint8_t *, size_t);
348
+ typedef int (*pq_import_pem_fn)(char **, uint8_t **, size_t *, const char *, size_t);
382
349
 
383
- if ((size_t)RSTRING_LEN(public_key) != PQ_HYBRID_PUBLICKEYBYTES) {
384
- rb_raise(rb_eArgError, "Invalid public key length");
385
- }
350
+ static VALUE pq_export_container_der(VALUE algorithm, VALUE key_bytes, pq_export_der_fn fn) {
351
+ uint8_t *out = NULL;
352
+ size_t out_len = 0;
353
+ VALUE result;
354
+ int ret;
386
355
 
387
- kem_encapsulate_call_t call = {0};
388
- size_t public_key_len = 0;
389
- call.public_key = pq_copy_ruby_string(public_key, &public_key_len);
390
- call.ciphertext = pq_alloc_buffer(PQ_HYBRID_CIPHERTEXTBYTES);
391
- call.shared_secret = pq_alloc_buffer(PQ_HYBRID_SHAREDSECRETBYTES);
356
+ StringValue(key_bytes);
357
+ ret = fn(&out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes),
358
+ (size_t)RSTRING_LEN(key_bytes), pq_algorithm_symbol_to_cstr(algorithm));
359
+ if (ret != PQ_SUCCESS)
360
+ pq_raise_general_error(ret);
392
361
 
393
- rb_thread_call_without_gvl(pq_hybrid_kem_encapsulate_nogvl, &call, NULL, NULL);
394
- pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
362
+ result = pq_string_from_buffer(out, out_len);
363
+ pq_secure_wipe(out, out_len);
364
+ free(out);
365
+ return result;
366
+ }
395
367
 
396
- if (call.result != PQ_SUCCESS) {
397
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
398
- free(call.ciphertext);
399
- pq_raise_general_error(call.result);
400
- }
368
+ static VALUE pq_export_container_pem(VALUE algorithm, VALUE key_bytes, pq_export_pem_fn fn) {
369
+ char *out = NULL;
370
+ size_t out_len = 0;
371
+ VALUE result;
372
+ int ret;
401
373
 
402
- VALUE result = rb_ary_new2(2);
403
- rb_ary_push(result, pq_string_from_buffer(call.ciphertext, PQ_HYBRID_CIPHERTEXTBYTES));
404
- rb_ary_push(result, pq_string_from_buffer(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES));
374
+ StringValue(key_bytes);
375
+ ret = fn(&out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes),
376
+ (size_t)RSTRING_LEN(key_bytes), pq_algorithm_symbol_to_cstr(algorithm));
377
+ if (ret != PQ_SUCCESS)
378
+ pq_raise_general_error(ret);
405
379
 
406
- free(call.ciphertext);
407
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
380
+ result = rb_utf8_str_new(out, (long)out_len);
381
+ pq_secure_wipe(out, out_len);
382
+ free(out);
408
383
  return result;
409
384
  }
410
385
 
411
- static VALUE pqcrypto_hybrid_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
412
- (void)self;
413
- StringValue(ciphertext);
414
- StringValue(secret_key);
386
+ static VALUE pq_import_container_der(VALUE der, pq_import_der_fn fn) {
387
+ char *algorithm = NULL;
388
+ uint8_t *key = NULL;
389
+ size_t key_len = 0;
390
+ VALUE result;
391
+ int ret;
392
+
393
+ StringValue(der);
394
+ ret =
395
+ fn(&algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
396
+ if (ret != PQ_SUCCESS)
397
+ pq_raise_general_error(ret);
415
398
 
416
- if ((size_t)RSTRING_LEN(ciphertext) != PQ_HYBRID_CIPHERTEXTBYTES) {
417
- rb_raise(rb_eArgError, "Invalid ciphertext length");
399
+ result = pq_build_algorithm_key_pair(algorithm, key, key_len);
400
+ free(algorithm);
401
+ pq_secure_wipe(key, key_len);
402
+ free(key);
403
+ return result;
404
+ }
405
+
406
+ static VALUE pq_import_container_pem(VALUE pem, pq_import_pem_fn fn) {
407
+ char *algorithm = NULL;
408
+ uint8_t *key = NULL;
409
+ size_t key_len = 0;
410
+ VALUE result;
411
+ int ret;
412
+
413
+ StringValue(pem);
414
+ ret = fn(&algorithm, &key, &key_len, RSTRING_PTR(pem), (size_t)RSTRING_LEN(pem));
415
+ if (ret != PQ_SUCCESS)
416
+ pq_raise_general_error(ret);
417
+
418
+ result = pq_build_algorithm_key_pair(algorithm, key, key_len);
419
+ free(algorithm);
420
+ pq_secure_wipe(key, key_len);
421
+ free(key);
422
+ return result;
423
+ }
424
+
425
+ __attribute__((noreturn)) static void pq_raise_general_error(int err) {
426
+ switch (err) {
427
+ case PQ_ERROR_KEYPAIR:
428
+ rb_raise(ePQCryptoError, "Keypair generation failed");
429
+ break;
430
+ case PQ_ERROR_ENCAPSULATE:
431
+ rb_raise(ePQCryptoError, "Encapsulation failed");
432
+ break;
433
+ case PQ_ERROR_DECAPSULATE:
434
+ rb_raise(ePQCryptoError, "Decapsulation failed");
435
+ break;
436
+ case PQ_ERROR_SIGN:
437
+ rb_raise(ePQCryptoError, "Signing failed");
438
+ break;
439
+ case PQ_ERROR_VERIFY:
440
+ rb_raise(ePQCryptoError, "Verification failed");
441
+ break;
442
+ case PQ_ERROR_RANDOM:
443
+ rb_raise(ePQCryptoError, "Random number generation failed");
444
+ break;
445
+ case PQ_ERROR_KDF:
446
+ rb_raise(ePQCryptoError, "Key derivation failed");
447
+ break;
448
+ case PQ_ERROR_BUFFER:
449
+ rb_raise(ePQCryptoError, "Buffer error");
450
+ break;
451
+ case PQ_ERROR_NOMEM:
452
+ rb_raise(rb_eNoMemError, "Memory allocation failed");
453
+ break;
454
+ case PQ_ERROR_OPENSSL:
455
+ rb_raise(ePQCryptoError, "OpenSSL error");
456
+ break;
457
+ default:
458
+ rb_raise(ePQCryptoError, "Unknown error: %d", err);
459
+ break;
418
460
  }
419
- if ((size_t)RSTRING_LEN(secret_key) != PQ_HYBRID_SECRETKEYBYTES) {
420
- rb_raise(rb_eArgError, "Invalid secret key length");
461
+ }
462
+
463
+ __attribute__((noreturn)) static void pq_raise_verification_error(int err) {
464
+ switch (err) {
465
+ case PQ_ERROR_VERIFY:
466
+ rb_raise(ePQCryptoVerificationError, "Verification failed");
467
+ break;
468
+ default:
469
+ pq_raise_general_error(err);
421
470
  }
471
+ }
422
472
 
423
- kem_decapsulate_call_t call = {0};
424
- size_t ciphertext_len = 0;
425
- size_t secret_key_len = 0;
426
- call.ciphertext = pq_copy_ruby_string(ciphertext, &ciphertext_len);
427
- call.secret_key = pq_copy_ruby_string(secret_key, &secret_key_len);
428
- call.shared_secret = pq_alloc_buffer(PQ_HYBRID_SHAREDSECRETBYTES);
473
+ static VALUE pqcrypto_ml_kem_keypair(VALUE self) {
474
+ (void)self;
475
+ return pq_run_kem_keypair(pq_ml_kem_keypair_nogvl, PQ_MLKEM_PUBLICKEYBYTES,
476
+ PQ_MLKEM_SECRETKEYBYTES);
477
+ }
429
478
 
430
- rb_thread_call_without_gvl(pq_hybrid_kem_decapsulate_nogvl, &call, NULL, NULL);
431
- pq_wipe_and_free((uint8_t *)call.ciphertext, ciphertext_len);
432
- pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
479
+ static VALUE pqcrypto_ml_kem_encapsulate(VALUE self, VALUE public_key) {
480
+ (void)self;
481
+ return pq_run_kem_encapsulate(pq_ml_kem_encapsulate_nogvl, public_key, PQ_MLKEM_PUBLICKEYBYTES,
482
+ PQ_MLKEM_CIPHERTEXTBYTES, PQ_MLKEM_SHAREDSECRETBYTES);
483
+ }
433
484
 
434
- if (call.result != PQ_SUCCESS) {
435
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
436
- pq_raise_general_error(call.result);
437
- }
485
+ static VALUE pqcrypto_ml_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
486
+ (void)self;
487
+ return pq_run_kem_decapsulate(pq_ml_kem_decapsulate_nogvl, ciphertext, PQ_MLKEM_CIPHERTEXTBYTES,
488
+ secret_key, PQ_MLKEM_SECRETKEYBYTES, PQ_MLKEM_SHAREDSECRETBYTES);
489
+ }
438
490
 
439
- VALUE result = pq_string_from_buffer(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
440
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
441
- return result;
491
+ static VALUE pqcrypto_hybrid_kem_keypair(VALUE self) {
492
+ (void)self;
493
+ return pq_run_kem_keypair(pq_hybrid_kem_keypair_nogvl, PQ_HYBRID_PUBLICKEYBYTES,
494
+ PQ_HYBRID_SECRETKEYBYTES);
495
+ }
496
+
497
+ static VALUE pqcrypto_hybrid_kem_encapsulate(VALUE self, VALUE public_key) {
498
+ (void)self;
499
+ return pq_run_kem_encapsulate(pq_hybrid_kem_encapsulate_nogvl, public_key,
500
+ PQ_HYBRID_PUBLICKEYBYTES, PQ_HYBRID_CIPHERTEXTBYTES,
501
+ PQ_HYBRID_SHAREDSECRETBYTES);
502
+ }
503
+
504
+ static VALUE pqcrypto_hybrid_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
505
+ (void)self;
506
+ return pq_run_kem_decapsulate(pq_hybrid_kem_decapsulate_nogvl, ciphertext,
507
+ PQ_HYBRID_CIPHERTEXTBYTES, secret_key, PQ_HYBRID_SECRETKEYBYTES,
508
+ PQ_HYBRID_SHAREDSECRETBYTES);
442
509
  }
443
510
 
444
511
  static VALUE pqcrypto__test_ml_kem_keypair_from_seed(VALUE self, VALUE seed) {
@@ -476,12 +543,9 @@ static VALUE pqcrypto__test_ml_kem_keypair_from_seed(VALUE self, VALUE seed) {
476
543
 
477
544
  static VALUE pqcrypto__test_ml_kem_encapsulate_from_seed(VALUE self, VALUE public_key, VALUE seed) {
478
545
  (void)self;
479
- StringValue(public_key);
546
+ pq_validate_bytes_argument(public_key, PQ_MLKEM_PUBLICKEYBYTES, "public key");
480
547
  StringValue(seed);
481
548
 
482
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLKEM_PUBLICKEYBYTES) {
483
- rb_raise(rb_eArgError, "Invalid public key length");
484
- }
485
549
  if ((size_t)RSTRING_LEN(seed) != 32) {
486
550
  rb_raise(rb_eArgError, "Deterministic test seed must be 32 bytes");
487
551
  }
@@ -550,12 +614,9 @@ static VALUE pqcrypto__test_sign_keypair_from_seed(VALUE self, VALUE seed) {
550
614
  static VALUE pqcrypto__test_sign_from_seed(VALUE self, VALUE message, VALUE secret_key,
551
615
  VALUE seed) {
552
616
  (void)self;
553
- StringValue(secret_key);
617
+ pq_validate_bytes_argument(secret_key, PQ_MLDSA_SECRETKEYBYTES, "secret key");
554
618
  StringValue(seed);
555
619
 
556
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLDSA_SECRETKEYBYTES) {
557
- rb_raise(rb_eArgError, "Invalid secret key length");
558
- }
559
620
  if ((size_t)RSTRING_LEN(seed) != 32) {
560
621
  rb_raise(rb_eArgError, "Deterministic test seed must be 32 bytes");
561
622
  }
@@ -588,35 +649,13 @@ static VALUE pqcrypto__test_sign_from_seed(VALUE self, VALUE message, VALUE secr
588
649
 
589
650
  static VALUE pqcrypto_sign_keypair(VALUE self) {
590
651
  (void)self;
591
-
592
- sign_keypair_call_t call = {0};
593
- call.public_key = pq_alloc_buffer(PQ_MLDSA_PUBLICKEYBYTES);
594
- call.secret_key = pq_alloc_buffer(PQ_MLDSA_SECRETKEYBYTES);
595
-
596
- rb_thread_call_without_gvl(pq_sign_keypair_nogvl, &call, NULL, NULL);
597
-
598
- if (call.result != PQ_SUCCESS) {
599
- pq_wipe_and_free(call.secret_key, PQ_MLDSA_SECRETKEYBYTES);
600
- free(call.public_key);
601
- pq_raise_general_error(call.result);
602
- }
603
-
604
- VALUE result = rb_ary_new2(2);
605
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_MLDSA_PUBLICKEYBYTES));
606
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_MLDSA_SECRETKEYBYTES));
607
-
608
- free(call.public_key);
609
- pq_wipe_and_free(call.secret_key, PQ_MLDSA_SECRETKEYBYTES);
610
- return result;
652
+ return pq_run_sign_keypair(pq_sign_keypair_nogvl, PQ_MLDSA_PUBLICKEYBYTES,
653
+ PQ_MLDSA_SECRETKEYBYTES);
611
654
  }
612
655
 
613
656
  static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
614
657
  (void)self;
615
- StringValue(secret_key);
616
-
617
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLDSA_SECRETKEYBYTES) {
618
- rb_raise(rb_eArgError, "Invalid secret key length");
619
- }
658
+ pq_validate_bytes_argument(secret_key, PQ_MLDSA_SECRETKEYBYTES, "secret key");
620
659
 
621
660
  sign_call_t call = {0};
622
661
  size_t secret_key_len = 0;
@@ -625,7 +664,7 @@ static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
625
664
  call.signature = pq_alloc_buffer(PQ_MLDSA_BYTES);
626
665
  call.message = pq_copy_ruby_string(message, &call.message_len);
627
666
 
628
- rb_thread_call_without_gvl(pq_sign_nogvl, &call, NULL, NULL);
667
+ rb_nogvl(pq_sign_nogvl, &call, NULL, NULL, RB_NOGVL_OFFLOAD_SAFE);
629
668
 
630
669
  pq_wipe_and_free(call.message, call.message_len);
631
670
  pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
@@ -643,11 +682,7 @@ static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
643
682
  static VALUE pqcrypto_verify(VALUE self, VALUE message, VALUE signature, VALUE public_key) {
644
683
  (void)self;
645
684
  StringValue(signature);
646
- StringValue(public_key);
647
-
648
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLDSA_PUBLICKEYBYTES) {
649
- rb_raise(rb_eArgError, "Invalid public key length");
650
- }
685
+ pq_validate_bytes_argument(public_key, PQ_MLDSA_PUBLICKEYBYTES, "public key");
651
686
 
652
687
  verify_call_t call = {0};
653
688
  size_t public_key_len = 0;
@@ -657,7 +692,7 @@ static VALUE pqcrypto_verify(VALUE self, VALUE message, VALUE signature, VALUE p
657
692
  call.signature_len = signature_len;
658
693
  call.message = pq_copy_ruby_string(message, &call.message_len);
659
694
 
660
- rb_thread_call_without_gvl(pq_verify_nogvl, &call, NULL, NULL);
695
+ rb_nogvl(pq_verify_nogvl, &call, NULL, NULL, RB_NOGVL_OFFLOAD_SAFE);
661
696
 
662
697
  pq_wipe_and_free(call.message, call.message_len);
663
698
  pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
@@ -700,146 +735,46 @@ static void define_constants(void) {
700
735
 
701
736
  static VALUE pqcrypto_public_key_to_pqc_container_der(VALUE self, VALUE algorithm,
702
737
  VALUE key_bytes) {
703
- uint8_t *out = NULL;
704
- size_t out_len = 0;
705
- VALUE result;
706
- StringValue(key_bytes);
707
- int ret = pq_public_key_to_pqc_container_der(
708
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
709
- pq_algorithm_symbol_to_cstr(algorithm));
710
- if (ret != PQ_SUCCESS)
711
- pq_raise_general_error(ret);
712
- result = pq_string_from_buffer(out, out_len);
713
- pq_secure_wipe(out, out_len);
714
- free(out);
715
- return result;
738
+ (void)self;
739
+ return pq_export_container_der(algorithm, key_bytes, pq_public_key_to_pqc_container_der);
716
740
  }
717
741
 
718
742
  static VALUE pqcrypto_public_key_to_pqc_container_pem(VALUE self, VALUE algorithm,
719
743
  VALUE key_bytes) {
720
- char *out = NULL;
721
- size_t out_len = 0;
722
- VALUE result;
723
- StringValue(key_bytes);
724
- int ret = pq_public_key_to_pqc_container_pem(
725
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
726
- pq_algorithm_symbol_to_cstr(algorithm));
727
- if (ret != PQ_SUCCESS)
728
- pq_raise_general_error(ret);
729
- result = rb_utf8_str_new(out, (long)out_len);
730
- pq_secure_wipe(out, out_len);
731
- free(out);
732
- return result;
744
+ (void)self;
745
+ return pq_export_container_pem(algorithm, key_bytes, pq_public_key_to_pqc_container_pem);
733
746
  }
734
747
 
735
748
  static VALUE pqcrypto_secret_key_to_pqc_container_der(VALUE self, VALUE algorithm,
736
749
  VALUE key_bytes) {
737
- uint8_t *out = NULL;
738
- size_t out_len = 0;
739
- VALUE result;
740
- StringValue(key_bytes);
741
- int ret = pq_secret_key_to_pqc_container_der(
742
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
743
- pq_algorithm_symbol_to_cstr(algorithm));
744
- if (ret != PQ_SUCCESS)
745
- pq_raise_general_error(ret);
746
- result = pq_string_from_buffer(out, out_len);
747
- pq_secure_wipe(out, out_len);
748
- free(out);
749
- return result;
750
+ (void)self;
751
+ return pq_export_container_der(algorithm, key_bytes, pq_secret_key_to_pqc_container_der);
750
752
  }
751
753
 
752
754
  static VALUE pqcrypto_secret_key_to_pqc_container_pem(VALUE self, VALUE algorithm,
753
755
  VALUE key_bytes) {
754
- char *out = NULL;
755
- size_t out_len = 0;
756
- VALUE result;
757
- StringValue(key_bytes);
758
- int ret = pq_secret_key_to_pqc_container_pem(
759
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
760
- pq_algorithm_symbol_to_cstr(algorithm));
761
- if (ret != PQ_SUCCESS)
762
- pq_raise_general_error(ret);
763
- result = rb_utf8_str_new(out, (long)out_len);
764
- pq_secure_wipe(out, out_len);
765
- free(out);
766
- return result;
756
+ (void)self;
757
+ return pq_export_container_pem(algorithm, key_bytes, pq_secret_key_to_pqc_container_pem);
767
758
  }
768
759
 
769
760
  static VALUE pqcrypto_public_key_from_pqc_container_der(VALUE self, VALUE der) {
770
- char *algorithm = NULL;
771
- uint8_t *key = NULL;
772
- size_t key_len = 0;
773
- VALUE ary;
774
- StringValue(der);
775
- int ret = pq_public_key_from_pqc_container_der(
776
- &algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
777
- if (ret != PQ_SUCCESS)
778
- pq_raise_general_error(ret);
779
- ary = rb_ary_new_capa(2);
780
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
781
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
782
- free(algorithm);
783
- pq_secure_wipe(key, key_len);
784
- free(key);
785
- return ary;
761
+ (void)self;
762
+ return pq_import_container_der(der, pq_public_key_from_pqc_container_der);
786
763
  }
787
764
 
788
765
  static VALUE pqcrypto_public_key_from_pqc_container_pem(VALUE self, VALUE pem) {
789
- char *algorithm = NULL;
790
- uint8_t *key = NULL;
791
- size_t key_len = 0;
792
- VALUE ary;
793
- StringValue(pem);
794
- int ret = pq_public_key_from_pqc_container_pem(&algorithm, &key, &key_len, RSTRING_PTR(pem),
795
- (size_t)RSTRING_LEN(pem));
796
- if (ret != PQ_SUCCESS)
797
- pq_raise_general_error(ret);
798
- ary = rb_ary_new_capa(2);
799
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
800
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
801
- free(algorithm);
802
- pq_secure_wipe(key, key_len);
803
- free(key);
804
- return ary;
766
+ (void)self;
767
+ return pq_import_container_pem(pem, pq_public_key_from_pqc_container_pem);
805
768
  }
806
769
 
807
770
  static VALUE pqcrypto_secret_key_from_pqc_container_der(VALUE self, VALUE der) {
808
- char *algorithm = NULL;
809
- uint8_t *key = NULL;
810
- size_t key_len = 0;
811
- VALUE ary;
812
- StringValue(der);
813
- int ret = pq_secret_key_from_pqc_container_der(
814
- &algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
815
- if (ret != PQ_SUCCESS)
816
- pq_raise_general_error(ret);
817
- ary = rb_ary_new_capa(2);
818
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
819
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
820
- free(algorithm);
821
- pq_secure_wipe(key, key_len);
822
- free(key);
823
- return ary;
771
+ (void)self;
772
+ return pq_import_container_der(der, pq_secret_key_from_pqc_container_der);
824
773
  }
825
774
 
826
775
  static VALUE pqcrypto_secret_key_from_pqc_container_pem(VALUE self, VALUE pem) {
827
- char *algorithm = NULL;
828
- uint8_t *key = NULL;
829
- size_t key_len = 0;
830
- VALUE ary;
831
- StringValue(pem);
832
- int ret = pq_secret_key_from_pqc_container_pem(&algorithm, &key, &key_len, RSTRING_PTR(pem),
833
- (size_t)RSTRING_LEN(pem));
834
- if (ret != PQ_SUCCESS)
835
- pq_raise_general_error(ret);
836
- ary = rb_ary_new_capa(2);
837
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
838
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
839
- free(algorithm);
840
- pq_secure_wipe(key, key_len);
841
- free(key);
842
- return ary;
776
+ (void)self;
777
+ return pq_import_container_pem(pem, pq_secret_key_from_pqc_container_pem);
843
778
  }
844
779
 
845
780
  void Init_pqcrypto_secure(void) {
@@ -705,69 +705,60 @@ static const char PQC_OID_ML_KEM_768[] = "2.25.186599352125448088867056807454444
705
705
  static const char PQC_OID_ML_KEM_768_X25519_HKDF_SHA256[] =
706
706
  "2.25.260242945110721168101139140490528778800";
707
707
  static const char PQC_OID_ML_DSA_65[] = "2.25.305232938483772195555080795650659207792";
708
-
709
- static int pq_serialization_key_bytes_for_algorithm(const char *algorithm, int is_public,
710
- size_t *expected_len) {
711
- if (!algorithm || !expected_len)
712
- return PQ_ERROR_BUFFER;
713
- if (strcmp(algorithm, "ml_kem_768") == 0) {
714
- *expected_len = is_public ? PQ_MLKEM_PUBLICKEYBYTES : PQ_MLKEM_SECRETKEYBYTES;
715
- return PQ_SUCCESS;
716
- }
717
- if (strcmp(algorithm, "ml_kem_768_x25519_hkdf_sha256") == 0) {
718
- *expected_len = is_public ? PQ_HYBRID_PUBLICKEYBYTES : PQ_HYBRID_SECRETKEYBYTES;
719
- return PQ_SUCCESS;
708
+ static const char PQC_PUBLIC_KEY_PEM_LABEL[] = "PQC PUBLIC KEY CONTAINER";
709
+ static const char PQC_PRIVATE_KEY_PEM_LABEL[] = "PQC PRIVATE KEY CONTAINER";
710
+
711
+ typedef struct {
712
+ const char *algorithm;
713
+ const char *oid;
714
+ size_t public_key_len;
715
+ size_t secret_key_len;
716
+ } pq_serialization_algorithm_t;
717
+
718
+ static const pq_serialization_algorithm_t PQC_SERIALIZATION_ALGORITHMS[] = {
719
+ {"ml_kem_768", PQC_OID_ML_KEM_768, PQ_MLKEM_PUBLICKEYBYTES, PQ_MLKEM_SECRETKEYBYTES},
720
+ {"ml_kem_768_x25519_hkdf_sha256", PQC_OID_ML_KEM_768_X25519_HKDF_SHA256,
721
+ PQ_HYBRID_PUBLICKEYBYTES, PQ_HYBRID_SECRETKEYBYTES},
722
+ {"ml_dsa_65", PQC_OID_ML_DSA_65, MLDSA_PUBLICKEYBYTES, MLDSA_SECRETKEYBYTES},
723
+ };
724
+
725
+ static const pq_serialization_algorithm_t *pq_find_serialization_algorithm(const char *algorithm) {
726
+ size_t i;
727
+
728
+ if (!algorithm)
729
+ return NULL;
730
+
731
+ for (i = 0; i < sizeof(PQC_SERIALIZATION_ALGORITHMS) / sizeof(PQC_SERIALIZATION_ALGORITHMS[0]);
732
+ ++i) {
733
+ if (strcmp(algorithm, PQC_SERIALIZATION_ALGORITHMS[i].algorithm) == 0)
734
+ return &PQC_SERIALIZATION_ALGORITHMS[i];
720
735
  }
721
- if (strcmp(algorithm, "ml_dsa_65") == 0) {
722
- *expected_len = is_public ? MLDSA_PUBLICKEYBYTES : MLDSA_SECRETKEYBYTES;
723
- return PQ_SUCCESS;
724
- }
725
- return PQ_ERROR_BUFFER;
726
- }
727
736
 
728
- static int pq_serialization_oid_for_algorithm(const char *algorithm, const char **oid_out) {
729
- if (!algorithm || !oid_out)
730
- return PQ_ERROR_BUFFER;
731
- if (strcmp(algorithm, "ml_kem_768") == 0) {
732
- *oid_out = PQC_OID_ML_KEM_768;
733
- return PQ_SUCCESS;
734
- }
735
- if (strcmp(algorithm, "ml_kem_768_x25519_hkdf_sha256") == 0) {
736
- *oid_out = PQC_OID_ML_KEM_768_X25519_HKDF_SHA256;
737
- return PQ_SUCCESS;
738
- }
739
- if (strcmp(algorithm, "ml_dsa_65") == 0) {
740
- *oid_out = PQC_OID_ML_DSA_65;
741
- return PQ_SUCCESS;
742
- }
743
- return PQ_ERROR_BUFFER;
737
+ return NULL;
744
738
  }
745
739
 
746
- static int pq_serialization_algorithm_for_oid(const char *oid, size_t oid_len,
747
- const char **algorithm_out) {
748
- if (!oid || !algorithm_out)
749
- return PQ_ERROR_BUFFER;
750
- if (oid_len == strlen(PQC_OID_ML_KEM_768) && memcmp(oid, PQC_OID_ML_KEM_768, oid_len) == 0) {
751
- *algorithm_out = "ml_kem_768";
752
- return PQ_SUCCESS;
753
- }
754
- if (oid_len == strlen(PQC_OID_ML_KEM_768_X25519_HKDF_SHA256) &&
755
- memcmp(oid, PQC_OID_ML_KEM_768_X25519_HKDF_SHA256, oid_len) == 0) {
756
- *algorithm_out = "ml_kem_768_x25519_hkdf_sha256";
757
- return PQ_SUCCESS;
758
- }
759
- if (oid_len == strlen(PQC_OID_ML_DSA_65) && memcmp(oid, PQC_OID_ML_DSA_65, oid_len) == 0) {
760
- *algorithm_out = "ml_dsa_65";
761
- return PQ_SUCCESS;
740
+ static const pq_serialization_algorithm_t *pq_find_serialization_algorithm_by_oid(const char *oid,
741
+ size_t oid_len) {
742
+ size_t i;
743
+
744
+ if (!oid)
745
+ return NULL;
746
+
747
+ for (i = 0; i < sizeof(PQC_SERIALIZATION_ALGORITHMS) / sizeof(PQC_SERIALIZATION_ALGORITHMS[0]);
748
+ ++i) {
749
+ const pq_serialization_algorithm_t *entry = &PQC_SERIALIZATION_ALGORITHMS[i];
750
+ if (oid_len == strlen(entry->oid) && memcmp(oid, entry->oid, oid_len) == 0)
751
+ return entry;
762
752
  }
763
- return PQ_ERROR_BUFFER;
753
+
754
+ return NULL;
764
755
  }
765
756
 
766
757
  static int pq_encode_serialized_key(uint8_t **output, size_t *output_len, uint8_t type,
767
758
  const uint8_t *key_bytes, size_t key_len,
768
759
  const char *algorithm) {
769
- const char *oid = NULL;
770
- size_t expected_len = 0;
760
+ const pq_serialization_algorithm_t *entry;
761
+ size_t expected_len;
771
762
  size_t oid_len;
772
763
  size_t total_len = 0;
773
764
  uint8_t *buf;
@@ -779,15 +770,16 @@ static int pq_encode_serialized_key(uint8_t **output, size_t *output_len, uint8_
779
770
  *output = NULL;
780
771
  *output_len = 0;
781
772
 
782
- ret = pq_serialization_key_bytes_for_algorithm(algorithm, type == PQC_SERIALIZATION_TYPE_PUBLIC,
783
- &expected_len);
784
- if (ret != PQ_SUCCESS || key_len != expected_len)
773
+ entry = pq_find_serialization_algorithm(algorithm);
774
+ if (!entry)
785
775
  return PQ_ERROR_BUFFER;
786
- ret = pq_serialization_oid_for_algorithm(algorithm, &oid);
787
- if (ret != PQ_SUCCESS)
788
- return ret;
789
776
 
790
- oid_len = strlen(oid);
777
+ expected_len =
778
+ (type == PQC_SERIALIZATION_TYPE_PUBLIC) ? entry->public_key_len : entry->secret_key_len;
779
+ if (key_len != expected_len)
780
+ return PQ_ERROR_BUFFER;
781
+
782
+ oid_len = strlen(entry->oid);
791
783
  if (oid_len == 0 || oid_len > UINT16_MAX)
792
784
  return PQ_ERROR_BUFFER;
793
785
  if (key_len > UINT32_MAX)
@@ -821,7 +813,7 @@ static int pq_encode_serialized_key(uint8_t **output, size_t *output_len, uint8_
821
813
  buf[5] = type;
822
814
  buf[6] = (uint8_t)((oid_len >> 8) & 0xFF);
823
815
  buf[7] = (uint8_t)(oid_len & 0xFF);
824
- memcpy(buf + 8, oid, oid_len);
816
+ memcpy(buf + 8, entry->oid, oid_len);
825
817
  buf[8 + oid_len + 0] = (uint8_t)((key_len >> 24) & 0xFF);
826
818
  buf[8 + oid_len + 1] = (uint8_t)((key_len >> 16) & 0xFF);
827
819
  buf[8 + oid_len + 2] = (uint8_t)((key_len >> 8) & 0xFF);
@@ -837,12 +829,11 @@ static int pq_decode_serialized_key(const uint8_t *input, size_t input_len, uint
837
829
  char **algorithm_out, uint8_t **key_out, size_t *key_len_out) {
838
830
  uint16_t oid_len;
839
831
  uint32_t key_len;
840
- const char *algorithm = NULL;
832
+ const pq_serialization_algorithm_t *entry;
841
833
  size_t offset;
842
834
  size_t expected_len = 0;
843
835
  uint8_t *key_copy = NULL;
844
836
  char *algorithm_copy = NULL;
845
- int ret;
846
837
 
847
838
  if (!input || !algorithm_out || !key_out || !key_len_out)
848
839
  return PQ_ERROR_BUFFER;
@@ -866,18 +857,18 @@ static int pq_decode_serialized_key(const uint8_t *input, size_t input_len, uint
866
857
  offset = 8;
867
858
  if (input_len < offset || input_len - offset < (size_t)oid_len + 4)
868
859
  return PQ_ERROR_BUFFER;
869
- ret = pq_serialization_algorithm_for_oid((const char *)(input + offset), oid_len, &algorithm);
870
- if (ret != PQ_SUCCESS)
871
- return ret;
860
+ entry = pq_find_serialization_algorithm_by_oid((const char *)(input + offset), oid_len);
861
+ if (!entry)
862
+ return PQ_ERROR_BUFFER;
872
863
  offset += oid_len;
873
864
  key_len = ((uint32_t)input[offset + 0] << 24) | ((uint32_t)input[offset + 1] << 16) |
874
865
  ((uint32_t)input[offset + 2] << 8) | (uint32_t)input[offset + 3];
875
866
  offset += 4;
876
867
  if (input_len < offset || input_len - offset != (size_t)key_len)
877
868
  return PQ_ERROR_BUFFER;
878
- ret = pq_serialization_key_bytes_for_algorithm(
879
- algorithm, expected_type == PQC_SERIALIZATION_TYPE_PUBLIC, &expected_len);
880
- if (ret != PQ_SUCCESS || (size_t)key_len != expected_len)
869
+ expected_len = (expected_type == PQC_SERIALIZATION_TYPE_PUBLIC) ? entry->public_key_len
870
+ : entry->secret_key_len;
871
+ if ((size_t)key_len != expected_len)
881
872
  return PQ_ERROR_BUFFER;
882
873
 
883
874
  key_copy = malloc((size_t)key_len);
@@ -886,14 +877,14 @@ static int pq_decode_serialized_key(const uint8_t *input, size_t input_len, uint
886
877
  memcpy(key_copy, input + offset, (size_t)key_len);
887
878
 
888
879
  {
889
- size_t algorithm_len = strlen(algorithm);
880
+ size_t algorithm_len = strlen(entry->algorithm);
890
881
  algorithm_copy = malloc(algorithm_len + 1);
891
882
  if (!algorithm_copy) {
892
883
  pq_secure_wipe(key_copy, (size_t)key_len);
893
884
  free(key_copy);
894
885
  return PQ_ERROR_NOMEM;
895
886
  }
896
- memcpy(algorithm_copy, algorithm, algorithm_len + 1);
887
+ memcpy(algorithm_copy, entry->algorithm, algorithm_len + 1);
897
888
  }
898
889
 
899
890
  *algorithm_out = algorithm_copy;
@@ -1104,7 +1095,7 @@ int pq_public_key_to_pqc_container_pem(char **output, size_t *output_len, const
1104
1095
  ret = pq_public_key_to_pqc_container_der(&der, &der_len, public_key, public_key_len, algorithm);
1105
1096
  if (ret != PQ_SUCCESS)
1106
1097
  return ret;
1107
- ret = pq_der_to_pem("PQC PUBLIC KEY CONTAINER", der, der_len, output, output_len);
1098
+ ret = pq_der_to_pem(PQC_PUBLIC_KEY_PEM_LABEL, der, der_len, output, output_len);
1108
1099
  pq_secure_wipe(der, der_len);
1109
1100
  free(der);
1110
1101
  return ret;
@@ -1125,7 +1116,7 @@ int pq_secret_key_to_pqc_container_pem(char **output, size_t *output_len, const
1125
1116
  ret = pq_secret_key_to_pqc_container_der(&der, &der_len, secret_key, secret_key_len, algorithm);
1126
1117
  if (ret != PQ_SUCCESS)
1127
1118
  return ret;
1128
- ret = pq_der_to_pem("PQC PRIVATE KEY CONTAINER", der, der_len, output, output_len);
1119
+ ret = pq_der_to_pem(PQC_PRIVATE_KEY_PEM_LABEL, der, der_len, output, output_len);
1129
1120
  pq_secure_wipe(der, der_len);
1130
1121
  free(der);
1131
1122
  return ret;
@@ -1143,7 +1134,7 @@ int pq_public_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out
1143
1134
  uint8_t *der = NULL;
1144
1135
  size_t der_len = 0;
1145
1136
  int ret;
1146
- ret = pq_pem_to_der("PQC PUBLIC KEY CONTAINER", input, input_len, &der, &der_len);
1137
+ ret = pq_pem_to_der(PQC_PUBLIC_KEY_PEM_LABEL, input, input_len, &der, &der_len);
1147
1138
  if (ret != PQ_SUCCESS)
1148
1139
  return ret;
1149
1140
  ret = pq_public_key_from_pqc_container_der(algorithm_out, key_out, key_len_out, der, der_len);
@@ -1164,7 +1155,7 @@ int pq_secret_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out
1164
1155
  uint8_t *der = NULL;
1165
1156
  size_t der_len = 0;
1166
1157
  int ret;
1167
- ret = pq_pem_to_der("PQC PRIVATE KEY CONTAINER", input, input_len, &der, &der_len);
1158
+ ret = pq_pem_to_der(PQC_PRIVATE_KEY_PEM_LABEL, input, input_len, &der, &der_len);
1168
1159
  if (ret != PQ_SUCCESS)
1169
1160
  return ret;
1170
1161
  ret = pq_secret_key_from_pqc_container_der(algorithm_out, key_out, key_len_out, der, der_len);
@@ -1174,5 +1165,5 @@ int pq_secret_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out
1174
1165
  }
1175
1166
 
1176
1167
  const char *pq_version(void) {
1177
- return "0.1.0";
1168
+ return "0.2.0";
1178
1169
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PQCrypto
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pq_crypto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-04-20 00:00:00.000000000 Z
10
+ date: 2026-04-22 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: rake
29
14
  requirement: !ruby/object:Gem::Requirement
@@ -173,7 +158,6 @@ metadata:
173
158
  homepage_uri: https://github.com/roman-haidarov/pq_crypto
174
159
  source_code_uri: https://github.com/roman-haidarov/pq_crypto
175
160
  changelog_uri: https://github.com/roman-haidarov/pq_crypto/blob/main/CHANGELOG.md
176
- post_install_message:
177
161
  rdoc_options: []
178
162
  require_paths:
179
163
  - lib
@@ -181,15 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
181
165
  requirements:
182
166
  - - ">="
183
167
  - !ruby/object:Gem::Version
184
- version: 3.1.0
168
+ version: 3.4.0.a
185
169
  required_rubygems_version: !ruby/object:Gem::Requirement
186
170
  requirements:
187
171
  - - ">="
188
172
  - !ruby/object:Gem::Version
189
173
  version: '0'
190
174
  requirements: []
191
- rubygems_version: 3.3.27
192
- signing_key:
175
+ rubygems_version: 3.6.2
193
176
  specification_version: 4
194
177
  summary: Primitive-first post-quantum cryptography for Ruby
195
178
  test_files: []