pq_crypto 0.1.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +37 -0
  3. data/CHANGELOG.md +29 -0
  4. data/GET_STARTED.md +65 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +135 -0
  7. data/SECURITY.md +57 -0
  8. data/ext/pqcrypto/extconf.rb +157 -0
  9. data/ext/pqcrypto/mldsa_api.h +51 -0
  10. data/ext/pqcrypto/mlkem_api.h +21 -0
  11. data/ext/pqcrypto/pqcrypto_ruby_secure.c +889 -0
  12. data/ext/pqcrypto/pqcrypto_secure.c +1178 -0
  13. data/ext/pqcrypto/pqcrypto_secure.h +135 -0
  14. data/ext/pqcrypto/vendor/.vendored +5 -0
  15. data/ext/pqcrypto/vendor/pqclean/common/aes.c +639 -0
  16. data/ext/pqcrypto/vendor/pqclean/common/aes.h +64 -0
  17. data/ext/pqcrypto/vendor/pqclean/common/compat.h +73 -0
  18. data/ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h +7 -0
  19. data/ext/pqcrypto/vendor/pqclean/common/fips202.c +928 -0
  20. data/ext/pqcrypto/vendor/pqclean/common/fips202.h +166 -0
  21. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S +168 -0
  22. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c +684 -0
  23. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h +60 -0
  24. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c +1028 -0
  25. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h +50 -0
  26. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros +198 -0
  27. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +8 -0
  28. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake +8 -0
  29. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h +3 -0
  30. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h +34 -0
  31. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h +142 -0
  32. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c +101 -0
  33. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h +39 -0
  34. data/ext/pqcrypto/vendor/pqclean/common/randombytes.c +355 -0
  35. data/ext/pqcrypto/vendor/pqclean/common/randombytes.h +27 -0
  36. data/ext/pqcrypto/vendor/pqclean/common/sha2.c +769 -0
  37. data/ext/pqcrypto/vendor/pqclean/common/sha2.h +173 -0
  38. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.c +156 -0
  39. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.h +27 -0
  40. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE +5 -0
  41. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +19 -0
  42. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake +23 -0
  43. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h +18 -0
  44. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c +83 -0
  45. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h +11 -0
  46. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c +327 -0
  47. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h +22 -0
  48. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c +164 -0
  49. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h +23 -0
  50. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c +146 -0
  51. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h +14 -0
  52. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h +36 -0
  53. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c +299 -0
  54. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h +37 -0
  55. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c +188 -0
  56. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h +26 -0
  57. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c +41 -0
  58. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h +13 -0
  59. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c +71 -0
  60. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h +30 -0
  61. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c +67 -0
  62. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h +13 -0
  63. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE +5 -0
  64. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +19 -0
  65. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake +23 -0
  66. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h +50 -0
  67. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c +98 -0
  68. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h +10 -0
  69. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c +261 -0
  70. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h +31 -0
  71. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h +44 -0
  72. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c +799 -0
  73. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h +52 -0
  74. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c +415 -0
  75. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h +65 -0
  76. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c +69 -0
  77. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h +17 -0
  78. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c +92 -0
  79. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h +14 -0
  80. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c +407 -0
  81. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h +47 -0
  82. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c +26 -0
  83. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h +34 -0
  84. data/lib/pq_crypto/errors.rb +10 -0
  85. data/lib/pq_crypto/hybrid_kem.rb +106 -0
  86. data/lib/pq_crypto/kem.rb +199 -0
  87. data/lib/pq_crypto/serialization.rb +102 -0
  88. data/lib/pq_crypto/signature.rb +198 -0
  89. data/lib/pq_crypto/version.rb +5 -0
  90. data/lib/pq_crypto.rb +177 -0
  91. data/lib/pqcrypto.rb +3 -0
  92. data/script/vendor_libs.rb +199 -0
  93. metadata +195 -0
data/lib/pq_crypto.rb ADDED
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require_relative "pq_crypto/version"
5
+ require_relative "pq_crypto/errors"
6
+
7
+ begin
8
+ require "pqcrypto/pqcrypto_secure"
9
+ rescue LoadError => original_error
10
+ ext_dir = File.expand_path("pqcrypto", __dir__)
11
+ extensions = [".#{RbConfig::CONFIG.fetch('DLEXT')}", ".bundle", ".so"].uniq
12
+ search_dirs = [ext_dir, File.join(ext_dir, "pqcrypto")].uniq
13
+ candidates = search_dirs.flat_map do |dir|
14
+ extensions.map { |ext| File.join(dir, "pqcrypto_secure#{ext}") }
15
+ end
16
+ existing = candidates.select { |path| File.exist?(path) }
17
+
18
+ raise LoadError,
19
+ "Could not find compiled PQCrypto extension. Run: bundle exec rake compile" if existing.empty?
20
+
21
+ loaded = existing.any? do |path|
22
+ begin
23
+ require path
24
+ true
25
+ rescue LoadError
26
+ false
27
+ end
28
+ end
29
+
30
+ raise original_error unless loaded
31
+ end
32
+
33
+ require_relative "pq_crypto/serialization"
34
+
35
+ module PQCrypto
36
+ SUITES = {
37
+ kem: [:ml_kem_768].freeze,
38
+ hybrid_kem: [:ml_kem_768_x25519_hkdf_sha256].freeze,
39
+ signature: [:ml_dsa_65].freeze,
40
+ }.freeze
41
+
42
+ NATIVE_EXTENSION_LOADED = true unless const_defined?(:NATIVE_EXTENSION_LOADED)
43
+
44
+ class << self
45
+ unless private_method_defined?(:native_ml_kem_keypair)
46
+ alias_method :native_ml_kem_keypair, :ml_kem_keypair
47
+ alias_method :native_ml_kem_encapsulate, :ml_kem_encapsulate
48
+ alias_method :native_ml_kem_decapsulate, :ml_kem_decapsulate
49
+ alias_method :native_hybrid_kem_keypair, :hybrid_kem_keypair
50
+ alias_method :native_hybrid_kem_encapsulate, :hybrid_kem_encapsulate
51
+ alias_method :native_hybrid_kem_decapsulate, :hybrid_kem_decapsulate
52
+ alias_method :native_sign_keypair, :sign_keypair
53
+ alias_method :native_sign, :sign
54
+ alias_method :native_verify, :verify
55
+ alias_method :native_secure_wipe, :secure_wipe
56
+ alias_method :native_version, :version
57
+ alias_method :native_public_key_to_pqc_container_der, :public_key_to_pqc_container_der
58
+ alias_method :native_public_key_to_pqc_container_pem, :public_key_to_pqc_container_pem
59
+ alias_method :native_secret_key_to_pqc_container_der, :secret_key_to_pqc_container_der
60
+ alias_method :native_secret_key_to_pqc_container_pem, :secret_key_to_pqc_container_pem
61
+ alias_method :native_public_key_from_pqc_container_der, :public_key_from_pqc_container_der
62
+ alias_method :native_public_key_from_pqc_container_pem, :public_key_from_pqc_container_pem
63
+ alias_method :native_secret_key_from_pqc_container_der, :secret_key_from_pqc_container_der
64
+ alias_method :native_secret_key_from_pqc_container_pem, :secret_key_from_pqc_container_pem
65
+ alias_method :native_test_ml_kem_keypair_from_seed, :__test_ml_kem_keypair_from_seed
66
+ alias_method :native_test_ml_kem_encapsulate_from_seed, :__test_ml_kem_encapsulate_from_seed
67
+ alias_method :native_test_sign_keypair_from_seed, :__test_sign_keypair_from_seed
68
+ alias_method :native_test_sign_from_seed, :__test_sign_from_seed
69
+
70
+ private :native_ml_kem_keypair,
71
+ :native_ml_kem_encapsulate,
72
+ :native_ml_kem_decapsulate,
73
+ :native_hybrid_kem_keypair,
74
+ :native_hybrid_kem_encapsulate,
75
+ :native_hybrid_kem_decapsulate,
76
+ :native_sign_keypair,
77
+ :native_sign,
78
+ :native_verify,
79
+ :native_secure_wipe,
80
+ :native_version,
81
+ :native_public_key_to_pqc_container_der,
82
+ :native_public_key_to_pqc_container_pem,
83
+ :native_secret_key_to_pqc_container_der,
84
+ :native_secret_key_to_pqc_container_pem,
85
+ :native_public_key_from_pqc_container_der,
86
+ :native_public_key_from_pqc_container_pem,
87
+ :native_secret_key_from_pqc_container_der,
88
+ :native_secret_key_from_pqc_container_pem,
89
+ :native_test_ml_kem_keypair_from_seed,
90
+ :native_test_ml_kem_encapsulate_from_seed,
91
+ :native_test_sign_keypair_from_seed,
92
+ :native_test_sign_from_seed,
93
+ :ml_kem_keypair,
94
+ :ml_kem_encapsulate,
95
+ :ml_kem_decapsulate,
96
+ :hybrid_kem_keypair,
97
+ :hybrid_kem_encapsulate,
98
+ :hybrid_kem_decapsulate,
99
+ :sign_keypair,
100
+ :sign,
101
+ :verify,
102
+ :public_key_to_pqc_container_der,
103
+ :public_key_to_pqc_container_pem,
104
+ :secret_key_to_pqc_container_der,
105
+ :secret_key_to_pqc_container_pem,
106
+ :public_key_from_pqc_container_der,
107
+ :public_key_from_pqc_container_pem,
108
+ :secret_key_from_pqc_container_der,
109
+ :secret_key_from_pqc_container_pem,
110
+ :__test_ml_kem_keypair_from_seed,
111
+ :__test_ml_kem_encapsulate_from_seed,
112
+ :__test_sign_keypair_from_seed,
113
+ :__test_sign_from_seed
114
+ end
115
+
116
+ def version
117
+ native_version
118
+ end
119
+
120
+ def backend
121
+ :native_pqclean
122
+ end
123
+
124
+ def native_extension_loaded?
125
+ true
126
+ end
127
+
128
+ def supported_kems
129
+ SUITES.fetch(:kem).dup
130
+ end
131
+
132
+ def supported_hybrid_kems
133
+ SUITES.fetch(:hybrid_kem).dup
134
+ end
135
+
136
+ def supported_signatures
137
+ SUITES.fetch(:signature).dup
138
+ end
139
+
140
+ def secure_wipe(string)
141
+ string = String(string)
142
+ raise ArgumentError, "secure_wipe requires a mutable String" if string.frozen?
143
+
144
+ native_secure_wipe(string)
145
+ end
146
+ end
147
+
148
+ module Testing
149
+ def self.ml_kem_keypair_from_seed(seed)
150
+ PQCrypto.__send__(:native_test_ml_kem_keypair_from_seed, String(seed).b)
151
+ rescue ArgumentError => e
152
+ raise InvalidKeyError, e.message
153
+ end
154
+
155
+ def self.ml_kem_encapsulate_from_seed(public_key, seed)
156
+ PQCrypto.__send__(:native_test_ml_kem_encapsulate_from_seed, String(public_key).b, String(seed).b)
157
+ rescue ArgumentError => e
158
+ raise InvalidKeyError, e.message
159
+ end
160
+
161
+ def self.ml_dsa_keypair_from_seed(seed)
162
+ PQCrypto.__send__(:native_test_sign_keypair_from_seed, String(seed).b)
163
+ rescue ArgumentError => e
164
+ raise InvalidKeyError, e.message
165
+ end
166
+
167
+ def self.ml_dsa_sign_from_seed(message, secret_key, seed)
168
+ PQCrypto.__send__(:native_test_sign_from_seed, String(message).b, String(secret_key).b, String(seed).b)
169
+ rescue ArgumentError => e
170
+ raise InvalidKeyError, e.message
171
+ end
172
+ end
173
+ end
174
+
175
+ require_relative "pq_crypto/kem"
176
+ require_relative "pq_crypto/hybrid_kem"
177
+ require_relative "pq_crypto/signature"
data/lib/pqcrypto.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pq_crypto"
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "digest"
5
+ require "fileutils"
6
+ require "open-uri"
7
+ require "rubygems/package"
8
+ require "tmpdir"
9
+ require "zlib"
10
+
11
+ VENDOR_DIR = File.expand_path("../ext/pqcrypto/vendor", __dir__)
12
+ MANIFEST_PATH = File.join(VENDOR_DIR, ".vendored")
13
+
14
+ DEFAULT_PQCLEAN = {
15
+ version: "2cc64716044832eea747234ddbffc06746ab815d",
16
+ url: "https://github.com/PQClean/PQClean/archive/2cc64716044832eea747234ddbffc06746ab815d.tar.gz",
17
+ strip: "PQClean-2cc64716044832eea747234ddbffc06746ab815d",
18
+ sha256: "0e92076a79082a8d220e27227f37b280fb2ce050af412babd2bc755ab37b871a"
19
+ }.freeze
20
+
21
+ KEEP_DIRS = %w[
22
+ crypto_kem/ml-kem-768/clean
23
+ crypto_sign/ml-dsa-65/clean
24
+ common
25
+ ].freeze
26
+
27
+ WARNING = <<~TEXT.freeze
28
+ WARNING: this script is a manual vendor refresh tool.
29
+
30
+ pq_crypto relies on the vendored PQClean snapshot committed to the repository.
31
+ Running this script will replace ext/pqcrypto/vendor with a fresh upstream copy.
32
+ Use the pinned defaults unless you are intentionally updating the upstream snapshot.
33
+ TEXT
34
+
35
+ def load_manifest(path)
36
+ return {} unless File.exist?(path)
37
+
38
+ File.readlines(path, chomp: true).each_with_object({}) do |line, acc|
39
+ next if line.strip.empty? || line.lstrip.start_with?("#")
40
+ key, value = line.split("=", 2)
41
+ next if key.nil? || value.nil?
42
+
43
+ acc[key] = value
44
+ end
45
+ end
46
+
47
+ def manifest_value(manifest, key)
48
+ value = manifest[key]
49
+ return nil if value.nil? || value.strip.empty? || value == "unrecorded"
50
+
51
+ value
52
+ end
53
+
54
+ def commit_archive_url?(url)
55
+ url.match?(%r{\Ahttps://(?:github\.com|codeload\.github\.com)/PQClean/PQClean/(?:archive|tar\.gz)/[0-9a-f]{40}(?:\.tar\.gz)?\z}i) ||
56
+ url.match?(%r{\Ahttps://github\.com/PQClean/PQClean/archive/[0-9a-f]{40}\.tar\.gz\z}i)
57
+ end
58
+
59
+ def build_vendor_config
60
+ manifest = load_manifest(MANIFEST_PATH)
61
+
62
+ version = ENV["PQCLEAN_VERSION"] || manifest_value(manifest, "pqclean_version") || DEFAULT_PQCLEAN[:version]
63
+ url = ENV["PQCLEAN_URL"] || manifest_value(manifest, "pqclean_url") || DEFAULT_PQCLEAN[:url]
64
+ sha256 = ENV["PQCLEAN_SHA256"] || manifest_value(manifest, "pqclean_archive_sha256") || DEFAULT_PQCLEAN[:sha256]
65
+ strip = ENV["PQCLEAN_STRIP"] || manifest_value(manifest, "pqclean_strip") || DEFAULT_PQCLEAN[:strip]
66
+
67
+ {
68
+ version: version,
69
+ url: url,
70
+ sha256: sha256,
71
+ strip: strip,
72
+ keep: KEEP_DIRS
73
+ }.freeze
74
+ end
75
+
76
+ def validate_vendor_config!(config)
77
+ required = %i[version url strip]
78
+ missing = required.select { |key| config[key].to_s.strip.empty? }
79
+ return if missing.empty?
80
+
81
+ abort <<~MSG
82
+ Missing required vendoring configuration: #{missing.join(", ")}
83
+
84
+ Example:
85
+ PQCLEAN_VERSION=<full-git-commit> \
86
+ PQCLEAN_URL=https://github.com/PQClean/PQClean/archive/<full-git-commit>.tar.gz \
87
+ PQCLEAN_STRIP=PQClean-<full-git-commit> \
88
+ PQCLEAN_SHA256=<archive-sha256> \
89
+ bundle exec ruby script/vendor_libs.rb
90
+ MSG
91
+ end
92
+
93
+ def validate_pinning!(config)
94
+ if config[:sha256].to_s.strip.empty?
95
+ abort <<~MSG
96
+ Refusing to vendor without PQCLEAN_SHA256.
97
+
98
+ Use the built-in pinned defaults, or provide all of:
99
+ PQCLEAN_VERSION
100
+ PQCLEAN_URL
101
+ PQCLEAN_STRIP
102
+ PQCLEAN_SHA256
103
+ MSG
104
+ end
105
+
106
+ return if commit_archive_url?(config[:url])
107
+
108
+ abort <<~MSG
109
+ Refusing to vendor from a non-commit archive URL.
110
+
111
+ Use a content-addressed full commit archive URL from the PQClean repository.
112
+ MSG
113
+ end
114
+
115
+ def download(url, destination)
116
+ puts "Downloading #{url}"
117
+ URI.open(url) { |remote| File.binwrite(destination, remote.read) }
118
+ end
119
+
120
+ def verify_checksum!(archive, expected_sha256)
121
+ actual = Digest::SHA256.file(archive).hexdigest
122
+ abort "SHA256 mismatch: expected #{expected_sha256}, got #{actual}" unless actual == expected_sha256
123
+
124
+ actual
125
+ end
126
+
127
+ def extract_subset(archive, destination, strip_prefix:, keep_dirs:)
128
+ prefix_re = /\A#{Regexp.escape(strip_prefix)}\//
129
+
130
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(archive)) do |tar|
131
+ tar.each do |entry|
132
+ relative_path = entry.full_name.sub(prefix_re, "")
133
+ next if relative_path.empty? || relative_path == entry.full_name
134
+ next unless keep_dirs.any? { |dir| relative_path.start_with?(dir) }
135
+
136
+ target = File.join(destination, relative_path)
137
+
138
+ if entry.directory?
139
+ FileUtils.mkdir_p(target)
140
+ elsif entry.file?
141
+ FileUtils.mkdir_p(File.dirname(target))
142
+ File.binwrite(target, entry.read)
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def write_manifest!(config:, archive_sha256:, tree_sha256:)
149
+ File.write(
150
+ MANIFEST_PATH,
151
+ <<~TEXT
152
+ pqclean_version=#{config[:version]}
153
+ pqclean_url=#{config[:url]}
154
+ pqclean_archive_sha256=#{archive_sha256}
155
+ pqclean_strip=#{config[:strip]}
156
+ pqclean_tree_sha256=#{tree_sha256}
157
+ TEXT
158
+ )
159
+ end
160
+
161
+ def tree_sha256_for(directory)
162
+ entries = Dir.glob(File.join(directory, "**", "*"), File::FNM_DOTMATCH)
163
+ .reject { |path| File.directory?(path) }
164
+ .sort
165
+
166
+ digest = Digest::SHA256.new
167
+ entries.each do |path|
168
+ relative = path.delete_prefix("#{directory}/")
169
+ digest << relative << "\0"
170
+ digest << File.binread(path)
171
+ digest << "\0"
172
+ end
173
+ digest.hexdigest
174
+ end
175
+
176
+ config = build_vendor_config
177
+ validate_vendor_config!(config)
178
+ validate_pinning!(config)
179
+
180
+ puts WARNING
181
+ puts "Vendoring PQClean into #{VENDOR_DIR}"
182
+ puts "Pinned ref: #{config[:version]}"
183
+ puts "Archive checksum: #{config[:sha256]}"
184
+
185
+ FileUtils.rm_rf(VENDOR_DIR)
186
+ FileUtils.mkdir_p(VENDOR_DIR)
187
+
188
+ Dir.mktmpdir("pq_crypto-vendor") do |tmpdir|
189
+ archive = File.join(tmpdir, "pqclean.tar.gz")
190
+ destination = File.join(VENDOR_DIR, "pqclean")
191
+
192
+ download(config[:url], archive)
193
+ archive_sha256 = verify_checksum!(archive, config[:sha256])
194
+ extract_subset(archive, destination, strip_prefix: config[:strip], keep_dirs: config[:keep])
195
+ write_manifest!(config: config, archive_sha256: archive_sha256, tree_sha256: tree_sha256_for(destination))
196
+ end
197
+
198
+ puts "Done. PQClean sources are now available in ext/pqcrypto/vendor/."
199
+ puts "Next step: review vendor diffs, then bundle exec rake compile"
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pq_crypto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Roman Haydarov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-04-20 00:00:00.000000000 Z
12
+ 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
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Native Ruby wrapper around ML-KEM-768, ML-DSA-65, and an optional hybrid
70
+ ML-KEM-768+X25519 KEM, backed by PQClean and OpenSSL.
71
+ email:
72
+ - romanhajdarov@gmail.com
73
+ executables: []
74
+ extensions:
75
+ - ext/pqcrypto/extconf.rb
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".github/workflows/ci.yml"
79
+ - CHANGELOG.md
80
+ - GET_STARTED.md
81
+ - LICENSE.txt
82
+ - README.md
83
+ - SECURITY.md
84
+ - ext/pqcrypto/extconf.rb
85
+ - ext/pqcrypto/mldsa_api.h
86
+ - ext/pqcrypto/mlkem_api.h
87
+ - ext/pqcrypto/pqcrypto_ruby_secure.c
88
+ - ext/pqcrypto/pqcrypto_secure.c
89
+ - ext/pqcrypto/pqcrypto_secure.h
90
+ - ext/pqcrypto/vendor/.vendored
91
+ - ext/pqcrypto/vendor/pqclean/common/aes.c
92
+ - ext/pqcrypto/vendor/pqclean/common/aes.h
93
+ - ext/pqcrypto/vendor/pqclean/common/compat.h
94
+ - ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h
95
+ - ext/pqcrypto/vendor/pqclean/common/fips202.c
96
+ - ext/pqcrypto/vendor/pqclean/common/fips202.h
97
+ - ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S
98
+ - ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c
99
+ - ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h
100
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c
101
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h
102
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros
103
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile
104
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake
105
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h
106
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h
107
+ - ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h
108
+ - ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c
109
+ - ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h
110
+ - ext/pqcrypto/vendor/pqclean/common/randombytes.c
111
+ - ext/pqcrypto/vendor/pqclean/common/randombytes.h
112
+ - ext/pqcrypto/vendor/pqclean/common/sha2.c
113
+ - ext/pqcrypto/vendor/pqclean/common/sha2.h
114
+ - ext/pqcrypto/vendor/pqclean/common/sp800-185.c
115
+ - ext/pqcrypto/vendor/pqclean/common/sp800-185.h
116
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE
117
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile
118
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake
119
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h
120
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c
121
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h
122
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c
123
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h
124
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c
125
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h
126
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c
127
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h
128
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h
129
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c
130
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h
131
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c
132
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h
133
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c
134
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h
135
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c
136
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h
137
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c
138
+ - ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h
139
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE
140
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile
141
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake
142
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h
143
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c
144
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h
145
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c
146
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h
147
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h
148
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c
149
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h
150
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c
151
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h
152
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c
153
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h
154
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c
155
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h
156
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c
157
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h
158
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c
159
+ - ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h
160
+ - lib/pq_crypto.rb
161
+ - lib/pq_crypto/errors.rb
162
+ - lib/pq_crypto/hybrid_kem.rb
163
+ - lib/pq_crypto/kem.rb
164
+ - lib/pq_crypto/serialization.rb
165
+ - lib/pq_crypto/signature.rb
166
+ - lib/pq_crypto/version.rb
167
+ - lib/pqcrypto.rb
168
+ - script/vendor_libs.rb
169
+ homepage: https://github.com/roman-haidarov/pq_crypto
170
+ licenses:
171
+ - MIT
172
+ metadata:
173
+ homepage_uri: https://github.com/roman-haidarov/pq_crypto
174
+ source_code_uri: https://github.com/roman-haidarov/pq_crypto
175
+ changelog_uri: https://github.com/roman-haidarov/pq_crypto/blob/main/CHANGELOG.md
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 3.1.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.3.27
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: Primitive-first post-quantum cryptography for Ruby
195
+ test_files: []