pq_crypto 0.3.2 → 0.5.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.
Files changed (328) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +56 -0
  3. data/CHANGELOG.md +62 -0
  4. data/GET_STARTED.md +366 -40
  5. data/README.md +76 -233
  6. data/SECURITY.md +107 -82
  7. data/ext/pqcrypto/extconf.rb +169 -87
  8. data/ext/pqcrypto/mldsa_api.h +1 -48
  9. data/ext/pqcrypto/mlkem_api.h +1 -18
  10. data/ext/pqcrypto/pq_externalmu.c +89 -204
  11. data/ext/pqcrypto/pqcrypto_native_api.h +129 -0
  12. data/ext/pqcrypto/pqcrypto_ruby_secure.c +484 -84
  13. data/ext/pqcrypto/pqcrypto_secure.c +203 -78
  14. data/ext/pqcrypto/pqcrypto_secure.h +53 -14
  15. data/ext/pqcrypto/pqcrypto_version.h +7 -0
  16. data/ext/pqcrypto/randombytes.h +9 -0
  17. data/ext/pqcrypto/vendor/.vendored +10 -5
  18. data/ext/pqcrypto/vendor/mldsa-native/BUILDING.md +105 -0
  19. data/ext/pqcrypto/vendor/mldsa-native/LICENSE +286 -0
  20. data/ext/pqcrypto/vendor/mldsa-native/META.yml +24 -0
  21. data/ext/pqcrypto/vendor/mldsa-native/README.md +221 -0
  22. data/ext/pqcrypto/vendor/mldsa-native/SECURITY.md +8 -0
  23. data/ext/pqcrypto/vendor/mldsa-native/mldsa/mldsa_native.c +721 -0
  24. data/ext/pqcrypto/vendor/mldsa-native/mldsa/mldsa_native.h +975 -0
  25. data/ext/pqcrypto/vendor/mldsa-native/mldsa/mldsa_native_asm.S +724 -0
  26. data/ext/pqcrypto/vendor/mldsa-native/mldsa/mldsa_native_config.h +723 -0
  27. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/cbmc.h +166 -0
  28. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/common.h +321 -0
  29. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/ct.c +21 -0
  30. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/ct.h +385 -0
  31. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/debug.c +73 -0
  32. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/debug.h +130 -0
  33. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/fips202.c +277 -0
  34. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/fips202.h +244 -0
  35. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/fips202x4.c +182 -0
  36. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/fips202x4.h +117 -0
  37. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/keccakf1600.c +438 -0
  38. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/keccakf1600.h +105 -0
  39. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/auto.h +71 -0
  40. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/fips202_native_aarch64.h +62 -0
  41. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccak_f1600_x1_scalar_asm.S +376 -0
  42. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccak_f1600_x1_v84a_asm.S +204 -0
  43. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccak_f1600_x2_v84a_asm.S +259 -0
  44. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccak_f1600_x4_v8a_scalar_hybrid_asm.S +1077 -0
  45. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccak_f1600_x4_v8a_v84a_scalar_hybrid_asm.S +987 -0
  46. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/src/keccakf1600_round_constants.c +41 -0
  47. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/x1_scalar.h +26 -0
  48. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/x1_v84a.h +35 -0
  49. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/x2_v84a.h +37 -0
  50. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/x4_v8a_scalar.h +27 -0
  51. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/aarch64/x4_v8a_v84a_scalar.h +36 -0
  52. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/api.h +69 -0
  53. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/README.md +10 -0
  54. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/mve.h +32 -0
  55. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/src/fips202_native_armv81m.h +20 -0
  56. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/src/keccak_f1600_x4_mve.S +638 -0
  57. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/src/keccak_f1600_x4_mve.c +136 -0
  58. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/armv81m/src/keccakf1600_round_constants.c +52 -0
  59. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/auto.h +29 -0
  60. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/x86_64/src/KeccakP_1600_times4_SIMD256.c +488 -0
  61. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/x86_64/src/KeccakP_1600_times4_SIMD256.h +16 -0
  62. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/fips202/native/x86_64/xkcp.h +31 -0
  63. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/meta.h +247 -0
  64. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/aarch64_zetas.c +231 -0
  65. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/arith_native_aarch64.h +150 -0
  66. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/intt.S +753 -0
  67. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/mld_polyvecl_pointwise_acc_montgomery_l4.S +129 -0
  68. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/mld_polyvecl_pointwise_acc_montgomery_l5.S +145 -0
  69. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/mld_polyvecl_pointwise_acc_montgomery_l7.S +177 -0
  70. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/ntt.S +653 -0
  71. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/pointwise_montgomery.S +79 -0
  72. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_caddq_asm.S +53 -0
  73. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_chknorm_asm.S +55 -0
  74. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_decompose_32_asm.S +85 -0
  75. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_decompose_88_asm.S +85 -0
  76. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_use_hint_32_asm.S +102 -0
  77. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/poly_use_hint_88_asm.S +110 -0
  78. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/polyz_unpack_17_asm.S +72 -0
  79. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/polyz_unpack_19_asm.S +69 -0
  80. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/polyz_unpack_table.c +40 -0
  81. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/rej_uniform_asm.S +189 -0
  82. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/rej_uniform_eta2_asm.S +135 -0
  83. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/rej_uniform_eta4_asm.S +128 -0
  84. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/rej_uniform_eta_table.c +543 -0
  85. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/aarch64/src/rej_uniform_table.c +62 -0
  86. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/api.h +649 -0
  87. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/meta.h +23 -0
  88. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/meta.h +315 -0
  89. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/arith_native_x86_64.h +124 -0
  90. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/consts.c +157 -0
  91. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/consts.h +27 -0
  92. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/intt.S +2311 -0
  93. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/ntt.S +2383 -0
  94. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/nttunpack.S +239 -0
  95. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/pointwise.S +131 -0
  96. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/pointwise_acc_l4.S +139 -0
  97. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/pointwise_acc_l5.S +155 -0
  98. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/pointwise_acc_l7.S +187 -0
  99. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_caddq_avx2.c +61 -0
  100. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_chknorm_avx2.c +52 -0
  101. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_decompose_32_avx2.c +155 -0
  102. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_decompose_88_avx2.c +155 -0
  103. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_use_hint_32_avx2.c +102 -0
  104. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/poly_use_hint_88_avx2.c +104 -0
  105. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/polyz_unpack_17_avx2.c +91 -0
  106. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/polyz_unpack_19_avx2.c +93 -0
  107. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/rej_uniform_avx2.c +126 -0
  108. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/rej_uniform_eta2_avx2.c +155 -0
  109. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/rej_uniform_eta4_avx2.c +139 -0
  110. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/native/x86_64/src/rej_uniform_table.c +160 -0
  111. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/packing.c +293 -0
  112. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/packing.h +224 -0
  113. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/params.h +77 -0
  114. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/poly.c +991 -0
  115. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/poly.h +393 -0
  116. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/poly_kl.c +946 -0
  117. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/poly_kl.h +360 -0
  118. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/polyvec.c +877 -0
  119. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/polyvec.h +725 -0
  120. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/randombytes.h +26 -0
  121. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/reduce.h +139 -0
  122. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/rounding.h +249 -0
  123. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/sign.c +1511 -0
  124. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/sign.h +806 -0
  125. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/symmetric.h +68 -0
  126. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/sys.h +268 -0
  127. data/ext/pqcrypto/vendor/mldsa-native/mldsa/src/zetas.inc +55 -0
  128. data/ext/pqcrypto/vendor/mlkem-native/BUILDING.md +104 -0
  129. data/ext/pqcrypto/vendor/mlkem-native/LICENSE +294 -0
  130. data/ext/pqcrypto/vendor/mlkem-native/META.yml +30 -0
  131. data/ext/pqcrypto/vendor/mlkem-native/README.md +223 -0
  132. data/ext/pqcrypto/vendor/mlkem-native/RELEASE.md +86 -0
  133. data/ext/pqcrypto/vendor/mlkem-native/SECURITY.md +8 -0
  134. data/ext/pqcrypto/vendor/mlkem-native/mlkem/README.md +23 -0
  135. data/ext/pqcrypto/vendor/mlkem-native/mlkem/mlkem_native.c +660 -0
  136. data/ext/pqcrypto/vendor/mlkem-native/mlkem/mlkem_native.h +538 -0
  137. data/ext/pqcrypto/vendor/mlkem-native/mlkem/mlkem_native_asm.S +681 -0
  138. data/ext/pqcrypto/vendor/mlkem-native/mlkem/mlkem_native_config.h +709 -0
  139. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/cbmc.h +174 -0
  140. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/common.h +274 -0
  141. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/compress.c +717 -0
  142. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/compress.h +688 -0
  143. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/debug.c +64 -0
  144. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/debug.h +128 -0
  145. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/fips202.c +251 -0
  146. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/fips202.h +158 -0
  147. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/fips202x4.c +208 -0
  148. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/fips202x4.h +80 -0
  149. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/keccakf1600.c +463 -0
  150. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/keccakf1600.h +98 -0
  151. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/auto.h +70 -0
  152. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/fips202_native_aarch64.h +69 -0
  153. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccak_f1600_x1_scalar_asm.S +375 -0
  154. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccak_f1600_x1_v84a_asm.S +203 -0
  155. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccak_f1600_x2_v84a_asm.S +258 -0
  156. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccak_f1600_x4_v8a_scalar_hybrid_asm.S +1076 -0
  157. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccak_f1600_x4_v8a_v84a_scalar_hybrid_asm.S +986 -0
  158. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/src/keccakf1600_round_constants.c +46 -0
  159. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/x1_scalar.h +25 -0
  160. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/x1_v84a.h +34 -0
  161. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/x2_v84a.h +35 -0
  162. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/x4_v8a_scalar.h +26 -0
  163. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/aarch64/x4_v8a_v84a_scalar.h +35 -0
  164. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/api.h +117 -0
  165. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/README.md +10 -0
  166. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/mve.h +79 -0
  167. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/fips202_native_armv81m.h +35 -0
  168. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/keccak_f1600_x4_mve.S +667 -0
  169. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/keccak_f1600_x4_mve.c +40 -0
  170. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/keccakf1600_round_constants.c +51 -0
  171. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/state_extract_bytes_x4_mve.S +290 -0
  172. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/armv81m/src/state_xor_bytes_x4_mve.S +314 -0
  173. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/auto.h +28 -0
  174. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/x86_64/keccak_f1600_x4_avx2.h +33 -0
  175. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/x86_64/src/fips202_native_x86_64.h +41 -0
  176. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/x86_64/src/keccak_f1600_x4_avx2.S +451 -0
  177. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/fips202/native/x86_64/src/keccakf1600_constants.c +51 -0
  178. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/indcpa.c +622 -0
  179. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/indcpa.h +156 -0
  180. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/kem.c +446 -0
  181. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/kem.h +326 -0
  182. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/README.md +16 -0
  183. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/meta.h +122 -0
  184. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/aarch64_zetas.c +174 -0
  185. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/arith_native_aarch64.h +177 -0
  186. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/intt.S +628 -0
  187. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/ntt.S +562 -0
  188. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/poly_mulcache_compute_asm.S +127 -0
  189. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/poly_reduce_asm.S +150 -0
  190. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/poly_tobytes_asm.S +117 -0
  191. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/poly_tomont_asm.S +98 -0
  192. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/polyvec_basemul_acc_montgomery_cached_asm_k2.S +261 -0
  193. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/polyvec_basemul_acc_montgomery_cached_asm_k3.S +314 -0
  194. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/polyvec_basemul_acc_montgomery_cached_asm_k4.S +368 -0
  195. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/rej_uniform_asm.S +226 -0
  196. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/aarch64/src/rej_uniform_table.c +542 -0
  197. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/api.h +637 -0
  198. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/meta.h +25 -0
  199. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/README.md +11 -0
  200. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/meta.h +128 -0
  201. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/arith_native_riscv64.h +45 -0
  202. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_debug.c +81 -0
  203. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_debug.h +145 -0
  204. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_izetas.inc +27 -0
  205. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_poly.c +805 -0
  206. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_zetas.inc +27 -0
  207. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/riscv64/src/rv64v_zetas_basemul.inc +39 -0
  208. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/README.md +4 -0
  209. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/meta.h +304 -0
  210. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/arith_native_x86_64.h +309 -0
  211. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/compress_consts.c +94 -0
  212. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/compress_consts.h +45 -0
  213. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/consts.c +102 -0
  214. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/consts.h +25 -0
  215. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/intt.S +719 -0
  216. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/mulcache_compute.S +90 -0
  217. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/ntt.S +639 -0
  218. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/nttfrombytes.S +193 -0
  219. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/ntttobytes.S +181 -0
  220. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/nttunpack.S +174 -0
  221. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_compress_d10.S +382 -0
  222. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_compress_d11.S +448 -0
  223. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_compress_d4.S +163 -0
  224. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_compress_d5.S +220 -0
  225. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_decompress_d10.S +228 -0
  226. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_decompress_d11.S +277 -0
  227. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_decompress_d4.S +180 -0
  228. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/poly_decompress_d5.S +192 -0
  229. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/polyvec_basemul_acc_montgomery_cached_asm_k2.S +502 -0
  230. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/polyvec_basemul_acc_montgomery_cached_asm_k3.S +750 -0
  231. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/polyvec_basemul_acc_montgomery_cached_asm_k4.S +998 -0
  232. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/reduce.S +218 -0
  233. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/rej_uniform_asm.S +103 -0
  234. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/rej_uniform_table.c +544 -0
  235. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/native/x86_64/src/tomont.S +155 -0
  236. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/params.h +76 -0
  237. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/poly.c +572 -0
  238. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/poly.h +317 -0
  239. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/poly_k.c +502 -0
  240. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/poly_k.h +668 -0
  241. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/randombytes.h +60 -0
  242. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/sampling.c +362 -0
  243. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/sampling.h +118 -0
  244. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/symmetric.h +70 -0
  245. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/sys.h +260 -0
  246. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/verify.c +20 -0
  247. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/verify.h +464 -0
  248. data/ext/pqcrypto/vendor/mlkem-native/mlkem/src/zetas.inc +30 -0
  249. data/lib/pq_crypto/algorithm_registry.rb +200 -0
  250. data/lib/pq_crypto/hybrid_kem.rb +1 -12
  251. data/lib/pq_crypto/kem.rb +104 -13
  252. data/lib/pq_crypto/pkcs8.rb +387 -0
  253. data/lib/pq_crypto/serialization.rb +1 -14
  254. data/lib/pq_crypto/signature.rb +123 -17
  255. data/lib/pq_crypto/spki.rb +131 -0
  256. data/lib/pq_crypto/version.rb +1 -1
  257. data/lib/pq_crypto.rb +79 -20
  258. data/script/vendor_libs.rb +88 -155
  259. metadata +241 -73
  260. data/ext/pqcrypto/vendor/pqclean/common/aes.c +0 -639
  261. data/ext/pqcrypto/vendor/pqclean/common/aes.h +0 -64
  262. data/ext/pqcrypto/vendor/pqclean/common/compat.h +0 -73
  263. data/ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h +0 -7
  264. data/ext/pqcrypto/vendor/pqclean/common/fips202.c +0 -928
  265. data/ext/pqcrypto/vendor/pqclean/common/fips202.h +0 -166
  266. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S +0 -168
  267. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c +0 -684
  268. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h +0 -60
  269. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c +0 -1028
  270. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h +0 -50
  271. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros +0 -198
  272. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +0 -8
  273. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake +0 -8
  274. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h +0 -3
  275. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h +0 -34
  276. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h +0 -142
  277. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c +0 -101
  278. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h +0 -39
  279. data/ext/pqcrypto/vendor/pqclean/common/randombytes.c +0 -355
  280. data/ext/pqcrypto/vendor/pqclean/common/randombytes.h +0 -27
  281. data/ext/pqcrypto/vendor/pqclean/common/sha2.c +0 -769
  282. data/ext/pqcrypto/vendor/pqclean/common/sha2.h +0 -173
  283. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.c +0 -156
  284. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.h +0 -27
  285. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE +0 -5
  286. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +0 -19
  287. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake +0 -23
  288. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h +0 -18
  289. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c +0 -83
  290. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h +0 -11
  291. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c +0 -327
  292. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h +0 -22
  293. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c +0 -164
  294. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h +0 -23
  295. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c +0 -146
  296. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h +0 -14
  297. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h +0 -36
  298. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c +0 -299
  299. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h +0 -37
  300. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c +0 -188
  301. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h +0 -26
  302. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c +0 -41
  303. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h +0 -13
  304. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c +0 -71
  305. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h +0 -30
  306. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c +0 -67
  307. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h +0 -13
  308. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE +0 -5
  309. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +0 -19
  310. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake +0 -23
  311. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h +0 -50
  312. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c +0 -98
  313. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h +0 -10
  314. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c +0 -261
  315. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h +0 -31
  316. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h +0 -44
  317. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c +0 -799
  318. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h +0 -52
  319. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c +0 -415
  320. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h +0 -65
  321. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c +0 -69
  322. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h +0 -17
  323. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c +0 -92
  324. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h +0 -14
  325. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c +0 -407
  326. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h +0 -47
  327. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c +0 -26
  328. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h +0 -34
@@ -4,18 +4,7 @@ module PQCrypto
4
4
  module HybridKEM
5
5
  CANONICAL_ALGORITHM = :ml_kem_768_x25519_xwing
6
6
 
7
- DETAILS = {
8
- CANONICAL_ALGORITHM => {
9
- name: CANONICAL_ALGORITHM,
10
- family: Serialization.algorithm_to_family(CANONICAL_ALGORITHM),
11
- oid: Serialization.algorithm_to_oid(CANONICAL_ALGORITHM),
12
- public_key_bytes: HYBRID_KEM_PUBLIC_KEY_BYTES,
13
- secret_key_bytes: HYBRID_KEM_SECRET_KEY_BYTES,
14
- ciphertext_bytes: HYBRID_KEM_CIPHERTEXT_BYTES,
15
- shared_secret_bytes: HYBRID_KEM_SHARED_SECRET_BYTES,
16
- description: "Hybrid KEM: ML-KEM-768 + X25519 combined via X-Wing SHA3-256 combiner (draft-connolly-cfrg-xwing-kem).",
17
- }.freeze,
18
- }.freeze
7
+ DETAILS = AlgorithmRegistry.details_for_family(:ml_kem_hybrid).freeze
19
8
 
20
9
  class << self
21
10
  def generate(algorithm = CANONICAL_ALGORITHM)
data/lib/pq_crypto/kem.rb CHANGED
@@ -6,23 +6,33 @@ module PQCrypto
6
6
  module KEM
7
7
  CANONICAL_ALGORITHM = :ml_kem_768
8
8
 
9
- DETAILS = {
10
- CANONICAL_ALGORITHM => {
11
- name: CANONICAL_ALGORITHM,
12
- family: Serialization.algorithm_to_family(CANONICAL_ALGORITHM),
13
- oid: Serialization.algorithm_to_oid(CANONICAL_ALGORITHM),
14
- public_key_bytes: ML_KEM_PUBLIC_KEY_BYTES,
15
- secret_key_bytes: ML_KEM_SECRET_KEY_BYTES,
16
- ciphertext_bytes: ML_KEM_CIPHERTEXT_BYTES,
17
- shared_secret_bytes: ML_KEM_SHARED_SECRET_BYTES,
18
- description: "Pure ML-KEM-768 primitive (FIPS 203).",
9
+ DETAILS = AlgorithmRegistry.details_for_family(:ml_kem).freeze
10
+
11
+ NATIVE_DISPATCH = {
12
+ ml_kem_512: {
13
+ keypair: :native_ml_kem_512_keypair,
14
+ keypair_from_seed: :native_ml_kem_512_keypair_from_seed,
15
+ encapsulate: :native_ml_kem_512_encapsulate,
16
+ decapsulate: :native_ml_kem_512_decapsulate,
17
+ }.freeze,
18
+ ml_kem_768: {
19
+ keypair: :native_ml_kem_keypair,
20
+ keypair_from_seed: :native_ml_kem_keypair_from_seed,
21
+ encapsulate: :native_ml_kem_encapsulate,
22
+ decapsulate: :native_ml_kem_decapsulate,
23
+ }.freeze,
24
+ ml_kem_1024: {
25
+ keypair: :native_ml_kem_1024_keypair,
26
+ keypair_from_seed: :native_ml_kem_1024_keypair_from_seed,
27
+ encapsulate: :native_ml_kem_1024_encapsulate,
28
+ decapsulate: :native_ml_kem_1024_decapsulate,
19
29
  }.freeze,
20
30
  }.freeze
21
31
 
22
32
  class << self
23
33
  def generate(algorithm = CANONICAL_ALGORITHM)
24
34
  algorithm = resolve_algorithm!(algorithm)
25
- public_key, secret_key = PQCrypto.__send__(:native_ml_kem_keypair)
35
+ public_key, secret_key = PQCrypto.__send__(native_method_for(algorithm, :keypair))
26
36
  Keypair.new(PublicKey.new(algorithm, public_key), SecretKey.new(algorithm, secret_key))
27
37
  end
28
38
 
@@ -54,6 +64,26 @@ module PQCrypto
54
64
  SecretKey.new(resolve_algorithm!(resolved_algorithm), bytes)
55
65
  end
56
66
 
67
+ def secret_key_from_pkcs8_der(der)
68
+ secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der))
69
+ end
70
+
71
+ def secret_key_from_pkcs8_pem(pem)
72
+ secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem))
73
+ end
74
+
75
+ def public_key_from_spki_der(der, algorithm: nil)
76
+ resolved_algorithm, bytes = SPKI.decode_der(der)
77
+ validate_algorithm_match!(algorithm, resolved_algorithm) if algorithm
78
+ PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
79
+ end
80
+
81
+ def public_key_from_spki_pem(pem, algorithm: nil)
82
+ resolved_algorithm, bytes = SPKI.decode_pem(pem)
83
+ validate_algorithm_match!(algorithm, resolved_algorithm) if algorithm
84
+ PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
85
+ end
86
+
57
87
  def details(algorithm)
58
88
  DETAILS.fetch(resolve_algorithm!(algorithm)).dup
59
89
  end
@@ -69,6 +99,37 @@ module PQCrypto
69
99
 
70
100
  raise UnsupportedAlgorithmError, "Unsupported KEM algorithm: #{algorithm.inspect}"
71
101
  end
102
+
103
+ def secret_key_from_decoded_pkcs8(algorithm, format, material)
104
+ secret_material = case format
105
+ when :seed
106
+ _public_key, expanded = PQCrypto.__send__(native_method_for(algorithm, :keypair_from_seed), material)
107
+ expanded
108
+ when :both
109
+ _seed, expanded = material
110
+ expanded
111
+ when :expanded
112
+ material
113
+ else
114
+ raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
115
+ end
116
+
117
+ SecretKey.new(resolve_algorithm!(algorithm), secret_material)
118
+ end
119
+
120
+ def native_method_for(algorithm, operation)
121
+ NATIVE_DISPATCH.fetch(resolve_algorithm!(algorithm)).fetch(operation)
122
+ end
123
+
124
+ def validate_algorithm_match!(expected_algorithm, actual_algorithm)
125
+ expected = resolve_algorithm!(expected_algorithm)
126
+ return if expected == actual_algorithm
127
+
128
+ raise SerializationError,
129
+ "Expected #{expected.inspect}, got #{actual_algorithm.inspect} (SPKI key algorithm mismatch)"
130
+ rescue UnsupportedAlgorithmError => e
131
+ raise SerializationError, e.message
132
+ end
72
133
  end
73
134
 
74
135
  class Keypair
@@ -109,8 +170,16 @@ module PQCrypto
109
170
  Serialization.public_key_to_pqc_container_pem(@algorithm, @bytes)
110
171
  end
111
172
 
173
+ def to_spki_der
174
+ SPKI.encode_der(@algorithm, @bytes)
175
+ end
176
+
177
+ def to_spki_pem
178
+ SPKI.encode_pem(@algorithm, @bytes)
179
+ end
180
+
112
181
  def encapsulate
113
- ciphertext, shared_secret = PQCrypto.__send__(:native_ml_kem_encapsulate, @bytes)
182
+ ciphertext, shared_secret = PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :encapsulate), @bytes)
114
183
  EncapsulationResult.new(ciphertext, shared_secret)
115
184
  rescue ArgumentError => e
116
185
  raise InvalidKeyError, e.message
@@ -165,8 +234,30 @@ module PQCrypto
165
234
  Serialization.secret_key_to_pqc_container_pem(@algorithm, @bytes)
166
235
  end
167
236
 
237
+ def to_pkcs8_der(format: :expanded)
238
+ case format
239
+ when :expanded
240
+ PKCS8.encode_der(@algorithm, @bytes, format: :expanded)
241
+ when :seed, :both
242
+ raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
243
+ else
244
+ raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
245
+ end
246
+ end
247
+
248
+ def to_pkcs8_pem(format: :expanded)
249
+ case format
250
+ when :expanded
251
+ PKCS8.encode_pem(@algorithm, @bytes, format: :expanded)
252
+ when :seed, :both
253
+ raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
254
+ else
255
+ raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
256
+ end
257
+ end
258
+
168
259
  def decapsulate(ciphertext)
169
- PQCrypto.__send__(:native_ml_kem_decapsulate, String(ciphertext).b, @bytes)
260
+ PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :decapsulate), String(ciphertext).b, @bytes)
170
261
  rescue ArgumentError => e
171
262
  raise InvalidCiphertextError, e.message
172
263
  end
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module PQCrypto
6
+ module PKCS8
7
+ PEM_LABEL = "PRIVATE KEY"
8
+ PEM_BEGIN = "-----BEGIN #{PEM_LABEL}-----"
9
+ PEM_END = "-----END #{PEM_LABEL}-----"
10
+ ML_KEM_SEED_BYTES = 64
11
+ ML_DSA_SEED_BYTES = 32
12
+
13
+ @allow_ml_dsa_seed_format = false
14
+
15
+ PRIVATE_KEY_CHOICES = {
16
+ ml_kem_512: {
17
+ seed_bytes: ML_KEM_SEED_BYTES,
18
+ expanded_bytes: PQCrypto::ML_KEM_512_SECRET_KEY_BYTES,
19
+ supported_formats: %i[seed expanded both],
20
+ }.freeze,
21
+ ml_kem_768: {
22
+ seed_bytes: ML_KEM_SEED_BYTES,
23
+ expanded_bytes: PQCrypto::ML_KEM_SECRET_KEY_BYTES,
24
+ supported_formats: %i[seed expanded both],
25
+ }.freeze,
26
+ ml_kem_1024: {
27
+ seed_bytes: ML_KEM_SEED_BYTES,
28
+ expanded_bytes: PQCrypto::ML_KEM_1024_SECRET_KEY_BYTES,
29
+ supported_formats: %i[seed expanded both],
30
+ }.freeze,
31
+ ml_dsa_44: {
32
+ seed_bytes: ML_DSA_SEED_BYTES,
33
+ expanded_bytes: PQCrypto::SIGN_44_SECRET_KEY_BYTES,
34
+ supported_formats: %i[seed expanded both],
35
+ }.freeze,
36
+ ml_dsa_65: {
37
+ seed_bytes: ML_DSA_SEED_BYTES,
38
+ expanded_bytes: PQCrypto::SIGN_SECRET_KEY_BYTES,
39
+ supported_formats: %i[seed expanded both],
40
+ }.freeze,
41
+ ml_dsa_87: {
42
+ seed_bytes: ML_DSA_SEED_BYTES,
43
+ expanded_bytes: PQCrypto::SIGN_87_SECRET_KEY_BYTES,
44
+ supported_formats: %i[seed expanded both],
45
+ }.freeze,
46
+ }.freeze
47
+
48
+ class << self
49
+ attr_accessor :allow_ml_dsa_seed_format
50
+
51
+ def encode_der(algorithm_symbol, secret_material, format:)
52
+ entry = AlgorithmRegistry.fetch(algorithm_symbol)
53
+ validate_secret_key_algorithm!(algorithm_symbol, entry)
54
+ ensure_format_supported!(algorithm_symbol, format)
55
+
56
+ choice_der = case format
57
+ when :seed
58
+ encode_seed_choice(secret_material, algorithm_symbol)
59
+ when :expanded
60
+ encode_expanded_key_choice(secret_material, algorithm_symbol)
61
+ when :both
62
+ encode_both_choice(secret_material, algorithm_symbol)
63
+ else
64
+ raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
65
+ end
66
+
67
+ OpenSSL::ASN1::Sequence.new([
68
+ OpenSSL::ASN1::Integer.new(0),
69
+ OpenSSL::ASN1::Sequence.new([
70
+ OpenSSL::ASN1::ObjectId.new(AlgorithmRegistry.standard_oid(algorithm_symbol)),
71
+ ]),
72
+ OpenSSL::ASN1::OctetString.new(choice_der),
73
+ ]).to_der.b
74
+ rescue OpenSSL::ASN1::ASN1Error => e
75
+ raise SerializationError, e.message
76
+ end
77
+
78
+ def encode_pem(algorithm_symbol, secret_material, format:)
79
+ der = encode_der(algorithm_symbol, secret_material, format: format)
80
+ body = encode_base64(der).scan(/.{1,64}/).join("\n")
81
+ "#{PEM_BEGIN}\n#{body}\n#{PEM_END}\n"
82
+ end
83
+
84
+ def decode_der(der)
85
+ input = String(der).b
86
+ outer = decode_asn1(input)
87
+ raise SerializationError, "PKCS#8 DER contains trailing data" unless outer.to_der.b == input
88
+ raise SerializationError, "PKCS#8 must be an ASN.1 SEQUENCE" unless outer.is_a?(OpenSSL::ASN1::Sequence)
89
+ raise SerializationError, "PKCS#8 OneAsymmetricKey must contain exactly 3 elements" unless outer.value.size == 3
90
+
91
+ version, algorithm_identifier, private_key = outer.value
92
+ decode_version(version)
93
+ algorithm = decode_algorithm_identifier(algorithm_identifier)
94
+ entry = AlgorithmRegistry.fetch(algorithm)
95
+ validate_secret_key_algorithm!(algorithm, entry)
96
+
97
+ unless private_key.is_a?(OpenSSL::ASN1::OctetString)
98
+ raise SerializationError, "PKCS#8 privateKey must be an OCTET STRING"
99
+ end
100
+
101
+ decode_private_key_choice(algorithm, String(private_key.value).b)
102
+ end
103
+
104
+ def decode_pem(pem)
105
+ der = der_from_pem(pem)
106
+ decode_der(der)
107
+ end
108
+
109
+ private
110
+
111
+ def decode_asn1(der)
112
+ OpenSSL::ASN1.decode(der)
113
+ rescue OpenSSL::ASN1::ASN1Error => e
114
+ raise SerializationError, e.message
115
+ end
116
+
117
+ def decode_version(value)
118
+ raise SerializationError, "PKCS#8 version must be an INTEGER" unless value.is_a?(OpenSSL::ASN1::Integer)
119
+
120
+ version = value.value.respond_to?(:to_i) ? value.value.to_i : value.value
121
+ raise SerializationError, "PKCS#8 version must be 0" unless version == 0
122
+ end
123
+
124
+ def decode_algorithm_identifier(value)
125
+ unless value.is_a?(OpenSSL::ASN1::Sequence)
126
+ raise SerializationError, "PKCS#8 algorithm must be an AlgorithmIdentifier SEQUENCE"
127
+ end
128
+ unless value.value.size == 1
129
+ raise SerializationError, "PKCS#8 AlgorithmIdentifier parameters must be absent"
130
+ end
131
+
132
+ oid = value.value.first
133
+ raise SerializationError, "PKCS#8 AlgorithmIdentifier must contain an OBJECT IDENTIFIER" unless oid.is_a?(OpenSSL::ASN1::ObjectId)
134
+
135
+ algorithm = AlgorithmRegistry.by_standard_oid(oid.oid)
136
+ raise SerializationError, "Unsupported PKCS#8 algorithm OID: #{oid.oid}" if algorithm.nil?
137
+
138
+ algorithm
139
+ end
140
+
141
+ def decode_private_key_choice(algorithm, choice_der)
142
+ tag = choice_der.getbyte(0)
143
+ raise SerializationError, "PKCS#8 privateKey CHOICE is empty" if tag.nil?
144
+
145
+ case tag
146
+ when 0x80
147
+ ensure_format_supported!(algorithm, :seed)
148
+ decode_seed_choice(algorithm, choice_der)
149
+ when 0x04
150
+ ensure_format_supported!(algorithm, :expanded)
151
+ decode_expanded_key(algorithm, choice_der)
152
+ when 0x30
153
+ ensure_format_supported!(algorithm, :both)
154
+ decode_both_choice(algorithm, choice_der)
155
+ else
156
+ raise SerializationError,
157
+ "Unsupported PKCS#8 #{algorithm.inspect} private key CHOICE tag: 0x#{tag.to_s(16).rjust(2, '0')}"
158
+ end
159
+ end
160
+
161
+ def decode_seed_choice(algorithm, choice_der)
162
+ seed = decode_tlv_value(choice_der, expected_tag: 0x80, label: "seed")
163
+ validate_seed_length!(algorithm, seed)
164
+
165
+ [algorithm, :seed, seed]
166
+ end
167
+
168
+ def decode_expanded_key(algorithm, choice_der)
169
+ expanded = decode_asn1(choice_der)
170
+ unless expanded.to_der.b == choice_der
171
+ raise SerializationError, "PKCS#8 expandedKey contains trailing data"
172
+ end
173
+ unless expanded.is_a?(OpenSSL::ASN1::OctetString)
174
+ raise SerializationError, "PKCS#8 expandedKey must be an OCTET STRING"
175
+ end
176
+
177
+ bytes = String(expanded.value).b
178
+ validate_expanded_key_length!(algorithm, bytes)
179
+
180
+ [algorithm, :expanded, bytes]
181
+ end
182
+
183
+ def decode_both_choice(algorithm, choice_der)
184
+ both = decode_asn1(choice_der)
185
+ raise SerializationError, "PKCS#8 both contains trailing data" unless both.to_der.b == choice_der
186
+ raise SerializationError, "PKCS#8 both must be a SEQUENCE" unless both.is_a?(OpenSSL::ASN1::Sequence)
187
+ raise SerializationError, "PKCS#8 both must contain exactly 2 elements" unless both.value.size == 2
188
+
189
+ seed, expanded = both.value
190
+ raise SerializationError, "PKCS#8 both seed must be an OCTET STRING" unless seed.is_a?(OpenSSL::ASN1::OctetString)
191
+ unless expanded.is_a?(OpenSSL::ASN1::OctetString)
192
+ raise SerializationError, "PKCS#8 both expandedKey must be an OCTET STRING"
193
+ end
194
+
195
+ seed_bytes = String(seed.value).b
196
+ expanded_bytes = String(expanded.value).b
197
+ validate_seed_length!(algorithm, seed_bytes)
198
+ validate_expanded_key_length!(algorithm, expanded_bytes)
199
+ verify_both_consistency!(algorithm, seed_bytes, expanded_bytes)
200
+
201
+ [algorithm, :both, [seed_bytes, expanded_bytes]]
202
+ end
203
+
204
+ def encode_seed_choice(secret_material, algorithm)
205
+ seed = String(secret_material).b
206
+ validate_seed_length!(algorithm, seed)
207
+
208
+ encode_tlv(0x80, seed)
209
+ end
210
+
211
+ def encode_expanded_key_choice(secret_material, algorithm)
212
+ bytes = String(secret_material).b
213
+ validate_expanded_key_length!(algorithm, bytes)
214
+
215
+ OpenSSL::ASN1::OctetString.new(bytes).to_der.b
216
+ end
217
+
218
+ def encode_both_choice(secret_material, algorithm)
219
+ unless secret_material.is_a?(Array) && secret_material.size == 2
220
+ raise SerializationError, "PKCS#8 both format requires [seed, expandedKey]"
221
+ end
222
+
223
+ seed, expanded = secret_material
224
+ seed_bytes = String(seed).b
225
+ expanded_bytes = String(expanded).b
226
+ validate_seed_length!(algorithm, seed_bytes)
227
+ validate_expanded_key_length!(algorithm, expanded_bytes)
228
+
229
+ OpenSSL::ASN1::Sequence.new([
230
+ OpenSSL::ASN1::OctetString.new(seed_bytes),
231
+ OpenSSL::ASN1::OctetString.new(expanded_bytes),
232
+ ]).to_der.b
233
+ end
234
+
235
+ def verify_both_consistency!(algorithm, seed, expanded)
236
+ native_method = {
237
+ ml_kem_512: :native_ml_kem_512_keypair_from_seed,
238
+ ml_kem_768: :native_ml_kem_keypair_from_seed,
239
+ ml_kem_1024: :native_ml_kem_1024_keypair_from_seed,
240
+ ml_dsa_44: :native_ml_dsa_44_keypair_from_seed,
241
+ ml_dsa_65: :native_ml_dsa_keypair_from_seed,
242
+ ml_dsa_87: :native_ml_dsa_87_keypair_from_seed,
243
+ }[algorithm]
244
+ return if native_method.nil?
245
+
246
+ _public_key, expected_expanded = PQCrypto.__send__(native_method, seed)
247
+ return if PQCrypto.__send__(:native_ct_equals, expected_expanded, expanded)
248
+
249
+ message = if ml_dsa_algorithm?(algorithm)
250
+ "seed/expandedKey inconsistency in ML-DSA PKCS#8 'both' encoding (RFC 9881 §6)"
251
+ else
252
+ "seed/expandedKey inconsistency in PKCS#8 'both' encoding (RFC 9935 §8)"
253
+ end
254
+ raise SerializationError, message
255
+ end
256
+
257
+ def validate_seed_length!(algorithm, seed)
258
+ expected = choice_profile(algorithm).fetch(:seed_bytes)
259
+ return if seed.bytesize == expected
260
+
261
+ raise SerializationError,
262
+ "Invalid #{algorithm.inspect} seed private key length: expected #{expected}, got #{seed.bytesize}"
263
+ end
264
+
265
+ def validate_expanded_key_length!(algorithm, expanded)
266
+ expected = choice_profile(algorithm).fetch(:expanded_bytes)
267
+ return if expanded.bytesize == expected
268
+
269
+ raise SerializationError,
270
+ "Invalid #{algorithm.inspect} expanded private key length: expected #{expected}, got #{expanded.bytesize}"
271
+ end
272
+
273
+ def validate_secret_key_algorithm!(algorithm_symbol, entry)
274
+ return if PRIVATE_KEY_CHOICES.key?(algorithm_symbol) && %i[ml_kem ml_dsa].include?(entry.fetch(:family))
275
+
276
+ raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm_symbol.inspect}"
277
+ end
278
+
279
+ def choice_profile(algorithm)
280
+ PRIVATE_KEY_CHOICES.fetch(algorithm) do
281
+ raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm.inspect}"
282
+ end
283
+ end
284
+
285
+ def ensure_format_supported!(algorithm, format)
286
+ if ml_dsa_algorithm?(algorithm) && %i[seed both].include?(format) && !allow_ml_dsa_seed_format
287
+ raise SerializationError,
288
+ "ML-DSA seed-format PKCS#8 is opt-in; set PQCrypto::PKCS8.allow_ml_dsa_seed_format = true to enable (see SECURITY.md for caveats)"
289
+ end
290
+
291
+ profile = choice_profile(algorithm)
292
+ return if profile.fetch(:supported_formats).include?(format)
293
+
294
+ raise SerializationError, "Unsupported PKCS#8 private key format for #{algorithm.inspect}: #{format.inspect}"
295
+ end
296
+
297
+ def ml_dsa_algorithm?(algorithm)
298
+ %i[ml_dsa_44 ml_dsa_65 ml_dsa_87].include?(algorithm)
299
+ end
300
+
301
+ def encode_tlv(tag, value)
302
+ tag.chr.b + encode_der_length(value.bytesize) + value
303
+ end
304
+
305
+ def decode_tlv_value(der, expected_tag:, label:)
306
+ tag = der.getbyte(0)
307
+ unless tag == expected_tag
308
+ raise SerializationError, "PKCS#8 #{label} has unexpected tag: 0x#{tag.to_s(16).rjust(2, '0')}"
309
+ end
310
+
311
+ length, length_bytes = decode_der_length(der, 1)
312
+ value_offset = 1 + length_bytes
313
+ value_end = value_offset + length
314
+ raise SerializationError, "PKCS#8 #{label} length exceeds available data" if value_end > der.bytesize
315
+ raise SerializationError, "PKCS#8 #{label} contains trailing data" unless value_end == der.bytesize
316
+
317
+ der.byteslice(value_offset, length).b
318
+ end
319
+
320
+ def encode_der_length(length)
321
+ raise SerializationError, "Invalid DER length" if length.negative?
322
+ return length.chr.b if length < 0x80
323
+
324
+ encoded = []
325
+ remaining = length
326
+ until remaining.zero?
327
+ encoded.unshift(remaining & 0xff)
328
+ remaining >>= 8
329
+ end
330
+
331
+ (0x80 | encoded.length).chr.b + encoded.pack("C*").b
332
+ end
333
+
334
+ def decode_der_length(der, offset)
335
+ first = der.getbyte(offset)
336
+ raise SerializationError, "PKCS#8 DER length is missing" if first.nil?
337
+
338
+ return [first, 1] if first < 0x80
339
+
340
+ length_octets = first & 0x7f
341
+ raise SerializationError, "PKCS#8 DER indefinite length is not allowed" if length_octets.zero?
342
+ raise SerializationError, "PKCS#8 DER length is too large" if length_octets > 4
343
+ if offset + 1 + length_octets > der.bytesize
344
+ raise SerializationError, "PKCS#8 DER length exceeds available data"
345
+ end
346
+
347
+ length = 0
348
+ length_octets.times do |i|
349
+ byte = der.getbyte(offset + 1 + i)
350
+ length = (length << 8) | byte
351
+ end
352
+
353
+ if length < 0x80 || (length_octets > 1 && der.getbyte(offset + 1).zero?)
354
+ raise SerializationError, "PKCS#8 DER length is not minimally encoded"
355
+ end
356
+
357
+ [length, 1 + length_octets]
358
+ end
359
+
360
+ def encode_base64(bytes)
361
+ [String(bytes).b].pack("m0")
362
+ end
363
+
364
+ def decode_base64(body)
365
+ compact = body.gsub(/[\r\n]/, "")
366
+ unless compact.match?(/\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?\z/)
367
+ raise SerializationError, "Invalid PKCS#8 PEM: invalid base64"
368
+ end
369
+
370
+ compact.unpack1("m0").b
371
+ rescue ArgumentError => e
372
+ raise SerializationError, e.message
373
+ end
374
+
375
+ def der_from_pem(pem)
376
+ text = String(pem)
377
+ match = text.match(/\A#{Regexp.escape(PEM_BEGIN)}\r?\n(?<body>[A-Za-z0-9+\/=\r\n]+)\r?\n#{Regexp.escape(PEM_END)}[ \t\r\n]*\z/)
378
+ raise SerializationError, "Invalid PKCS#8 PEM: expected #{PEM_LABEL.inspect} label" unless match
379
+
380
+ body = match[:body]
381
+ raise SerializationError, "Invalid PKCS#8 PEM: embedded NUL in body" if body.include?("\0")
382
+
383
+ decode_base64(body)
384
+ end
385
+ end
386
+ end
387
+ end
@@ -2,20 +2,7 @@
2
2
 
3
3
  module PQCrypto
4
4
  module Serialization
5
- ALGORITHM_METADATA = {
6
- ml_kem_768: {
7
- family: :ml_kem,
8
- oid: "2.25.186599352125448088867056807454444238446",
9
- }.freeze,
10
- ml_kem_768_x25519_xwing: {
11
- family: :ml_kem_hybrid,
12
- oid: "1.3.6.1.4.1.62253.25722",
13
- }.freeze,
14
- ml_dsa_65: {
15
- family: :ml_dsa,
16
- oid: "2.25.305232938483772195555080795650659207792",
17
- }.freeze,
18
- }.freeze
5
+ ALGORITHM_METADATA = AlgorithmRegistry.legacy_metadata_view.freeze
19
6
 
20
7
  class << self
21
8
  def algorithm_metadata(algorithm)