iodine 0.7.60 → 0.8.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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -0,0 +1,1441 @@
1
+ #ifndef H___IODINE_CRYPTO___H
2
+ #define H___IODINE_CRYPTO___H
3
+ #include "iodine.h"
4
+
5
+ /* *****************************************************************************
6
+ Iodine::Base::Crypto - Advanced Cryptographic Operations
7
+
8
+ Provides access to modern cryptographic primitives:
9
+ - ChaCha20-Poly1305: AEAD symmetric encryption (12-byte nonce)
10
+ - XChaCha20-Poly1305: AEAD symmetric encryption (24-byte nonce, safe for random)
11
+ - AES-128-GCM: AEAD symmetric encryption (16-byte key, 12-byte nonce)
12
+ - AES-256-GCM: AEAD symmetric encryption (32-byte key, 12-byte nonce)
13
+ - Ed25519: Digital signatures
14
+ - X25519: Key exchange and public-key encryption (ECIES with ChaCha20/AES)
15
+ - HKDF: Key derivation (RFC 5869)
16
+ ***************************************************************************** */
17
+
18
+ static VALUE iodine_rb_CRYPTO;
19
+ static VALUE iodine_rb_CHACHA20POLY1305;
20
+ static VALUE iodine_rb_XCHACHA20POLY1305;
21
+ static VALUE iodine_rb_AES128GCM;
22
+ static VALUE iodine_rb_AES256GCM;
23
+ static VALUE iodine_rb_ED25519;
24
+ static VALUE iodine_rb_X25519;
25
+ static VALUE iodine_rb_HKDF;
26
+ static VALUE iodine_rb_X25519MLKEM768;
27
+ static VALUE iodine_rb_ARGON2;
28
+ static VALUE iodine_rb_LYRA2;
29
+
30
+ /* *****************************************************************************
31
+ ChaCha20-Poly1305 AEAD Encryption
32
+ ***************************************************************************** */
33
+
34
+ /**
35
+ * Encrypts data using ChaCha20-Poly1305 AEAD.
36
+ *
37
+ * @param data [String] Plaintext to encrypt
38
+ * @param key: [String] 32-byte encryption key
39
+ * @param nonce: [String] 12-byte nonce (must be unique per key)
40
+ * @param ad: [String, nil] Optional additional authenticated data
41
+ * @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
42
+ */
43
+ FIO_SFUNC VALUE iodine_crypto_chacha_encrypt(int argc,
44
+ VALUE *argv,
45
+ VALUE self) {
46
+ fio_buf_info_s data = FIO_BUF_INFO0;
47
+ fio_buf_info_s key = FIO_BUF_INFO0;
48
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
49
+ fio_buf_info_s ad = FIO_BUF_INFO0;
50
+
51
+ iodine_rb2c_arg(argc,
52
+ argv,
53
+ IODINE_ARG_BUF(data, 0, "data", 1),
54
+ IODINE_ARG_BUF(key, 0, "key", 1),
55
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
56
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
57
+
58
+ if (key.len != 32)
59
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
60
+ if (nonce.len != 12)
61
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
62
+
63
+ /* Copy data for in-place encryption */
64
+ VALUE ciphertext = rb_str_new(data.buf, data.len);
65
+ uint8_t mac[16];
66
+
67
+ fio_chacha20_poly1305_enc(mac,
68
+ RSTRING_PTR(ciphertext),
69
+ data.len,
70
+ ad.buf,
71
+ ad.len,
72
+ (void *)key.buf,
73
+ (void *)nonce.buf);
74
+
75
+ VALUE mac_str = rb_str_new((const char *)mac, 16);
76
+ return rb_ary_new_from_args(2, ciphertext, mac_str);
77
+ (void)self;
78
+ }
79
+
80
+ /**
81
+ * Decrypts data using ChaCha20-Poly1305 AEAD.
82
+ *
83
+ * @param data [String] Ciphertext to decrypt
84
+ * @param mac: [String] 16-byte authentication tag
85
+ * @param key: [String] 32-byte encryption key
86
+ * @param nonce: [String] 12-byte nonce
87
+ * @param ad: [String, nil] Optional additional authenticated data
88
+ * @return [String] Decrypted plaintext
89
+ * @raise [RuntimeError] if authentication fails
90
+ */
91
+ FIO_SFUNC VALUE iodine_crypto_chacha_decrypt(int argc,
92
+ VALUE *argv,
93
+ VALUE self) {
94
+ fio_buf_info_s data = FIO_BUF_INFO0;
95
+ fio_buf_info_s mac = FIO_BUF_INFO0;
96
+ fio_buf_info_s key = FIO_BUF_INFO0;
97
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
98
+ fio_buf_info_s ad = FIO_BUF_INFO0;
99
+
100
+ iodine_rb2c_arg(argc,
101
+ argv,
102
+ IODINE_ARG_BUF(data, 0, "data", 1),
103
+ IODINE_ARG_BUF(mac, 0, "mac", 1),
104
+ IODINE_ARG_BUF(key, 0, "key", 1),
105
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
106
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
107
+
108
+ if (key.len != 32)
109
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
110
+ if (nonce.len != 12)
111
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
112
+ if (mac.len != 16)
113
+ rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
114
+
115
+ /* Copy data for in-place decryption, also copy mac since it's modified */
116
+ VALUE plaintext = rb_str_new(data.buf, data.len);
117
+ uint8_t mac_copy[16];
118
+ FIO_MEMCPY(mac_copy, mac.buf, 16);
119
+
120
+ int result = fio_chacha20_poly1305_dec(mac_copy,
121
+ RSTRING_PTR(plaintext),
122
+ data.len,
123
+ ad.buf,
124
+ ad.len,
125
+ (void *)key.buf,
126
+ (void *)nonce.buf);
127
+
128
+ if (result != 0)
129
+ rb_raise(rb_eRuntimeError, "Authentication failed");
130
+
131
+ return plaintext;
132
+ (void)self;
133
+ }
134
+
135
+ /* *****************************************************************************
136
+ XChaCha20-Poly1305 AEAD Encryption (Extended Nonce)
137
+ ***************************************************************************** */
138
+
139
+ /**
140
+ * Encrypts data using XChaCha20-Poly1305 AEAD.
141
+ *
142
+ * XChaCha20-Poly1305 uses a 24-byte nonce (vs 12-byte for ChaCha20-Poly1305),
143
+ * making it safe to use randomly generated nonces without risk of collision.
144
+ *
145
+ * @param data [String] Plaintext to encrypt
146
+ * @param key: [String] 32-byte encryption key
147
+ * @param nonce: [String] 24-byte nonce (safe for random generation)
148
+ * @param ad: [String, nil] Optional additional authenticated data
149
+ * @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
150
+ */
151
+ FIO_SFUNC VALUE iodine_crypto_xchacha_encrypt(int argc,
152
+ VALUE *argv,
153
+ VALUE self) {
154
+ fio_buf_info_s data = FIO_BUF_INFO0;
155
+ fio_buf_info_s key = FIO_BUF_INFO0;
156
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
157
+ fio_buf_info_s ad = FIO_BUF_INFO0;
158
+
159
+ iodine_rb2c_arg(argc,
160
+ argv,
161
+ IODINE_ARG_BUF(data, 0, "data", 1),
162
+ IODINE_ARG_BUF(key, 0, "key", 1),
163
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
164
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
165
+
166
+ if (key.len != 32)
167
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
168
+ if (nonce.len != 24)
169
+ rb_raise(rb_eArgError, "nonce must be 24 bytes (got %zu)", nonce.len);
170
+
171
+ /* Copy data for in-place encryption */
172
+ VALUE ciphertext = rb_str_new(data.buf, data.len);
173
+ uint8_t mac[16];
174
+
175
+ fio_xchacha20_poly1305_enc(mac,
176
+ RSTRING_PTR(ciphertext),
177
+ data.len,
178
+ ad.buf,
179
+ ad.len,
180
+ (void *)key.buf,
181
+ (void *)nonce.buf);
182
+
183
+ VALUE mac_str = rb_str_new((const char *)mac, 16);
184
+ return rb_ary_new_from_args(2, ciphertext, mac_str);
185
+ (void)self;
186
+ }
187
+
188
+ /**
189
+ * Decrypts data using XChaCha20-Poly1305 AEAD.
190
+ *
191
+ * @param data [String] Ciphertext to decrypt
192
+ * @param mac: [String] 16-byte authentication tag
193
+ * @param key: [String] 32-byte encryption key
194
+ * @param nonce: [String] 24-byte nonce
195
+ * @param ad: [String, nil] Optional additional authenticated data
196
+ * @return [String] Decrypted plaintext
197
+ * @raise [RuntimeError] if authentication fails
198
+ */
199
+ FIO_SFUNC VALUE iodine_crypto_xchacha_decrypt(int argc,
200
+ VALUE *argv,
201
+ VALUE self) {
202
+ fio_buf_info_s data = FIO_BUF_INFO0;
203
+ fio_buf_info_s mac = FIO_BUF_INFO0;
204
+ fio_buf_info_s key = FIO_BUF_INFO0;
205
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
206
+ fio_buf_info_s ad = FIO_BUF_INFO0;
207
+
208
+ iodine_rb2c_arg(argc,
209
+ argv,
210
+ IODINE_ARG_BUF(data, 0, "data", 1),
211
+ IODINE_ARG_BUF(mac, 0, "mac", 1),
212
+ IODINE_ARG_BUF(key, 0, "key", 1),
213
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
214
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
215
+
216
+ if (key.len != 32)
217
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
218
+ if (nonce.len != 24)
219
+ rb_raise(rb_eArgError, "nonce must be 24 bytes (got %zu)", nonce.len);
220
+ if (mac.len != 16)
221
+ rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
222
+
223
+ /* Copy data for in-place decryption, also copy mac since it's modified */
224
+ VALUE plaintext = rb_str_new(data.buf, data.len);
225
+ uint8_t mac_copy[16];
226
+ FIO_MEMCPY(mac_copy, mac.buf, 16);
227
+
228
+ int result = fio_xchacha20_poly1305_dec(mac_copy,
229
+ RSTRING_PTR(plaintext),
230
+ data.len,
231
+ ad.buf,
232
+ ad.len,
233
+ (void *)key.buf,
234
+ (void *)nonce.buf);
235
+
236
+ if (result != 0)
237
+ rb_raise(rb_eRuntimeError, "Authentication failed");
238
+
239
+ return plaintext;
240
+ (void)self;
241
+ }
242
+
243
+ /* *****************************************************************************
244
+ AES-128-GCM AEAD Encryption
245
+ ***************************************************************************** */
246
+
247
+ /**
248
+ * Encrypts data using AES-128-GCM AEAD.
249
+ *
250
+ * @param data [String] Plaintext to encrypt
251
+ * @param key: [String] 16-byte encryption key
252
+ * @param nonce: [String] 12-byte nonce (must be unique per key)
253
+ * @param ad: [String, nil] Optional additional authenticated data
254
+ * @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
255
+ */
256
+ FIO_SFUNC VALUE iodine_crypto_aes128gcm_encrypt(int argc,
257
+ VALUE *argv,
258
+ VALUE self) {
259
+ fio_buf_info_s data = FIO_BUF_INFO0;
260
+ fio_buf_info_s key = FIO_BUF_INFO0;
261
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
262
+ fio_buf_info_s ad = FIO_BUF_INFO0;
263
+
264
+ iodine_rb2c_arg(argc,
265
+ argv,
266
+ IODINE_ARG_BUF(data, 0, "data", 1),
267
+ IODINE_ARG_BUF(key, 0, "key", 1),
268
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
269
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
270
+
271
+ if (key.len != 16)
272
+ rb_raise(rb_eArgError, "key must be 16 bytes (got %zu)", key.len);
273
+ if (nonce.len != 12)
274
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
275
+
276
+ /* Copy data for in-place encryption */
277
+ VALUE ciphertext = rb_str_new(data.buf, data.len);
278
+ uint8_t mac[16];
279
+
280
+ fio_aes128_gcm_enc(mac,
281
+ RSTRING_PTR(ciphertext),
282
+ data.len,
283
+ ad.buf,
284
+ ad.len,
285
+ (void *)key.buf,
286
+ (void *)nonce.buf);
287
+
288
+ VALUE mac_str = rb_str_new((const char *)mac, 16);
289
+ return rb_ary_new_from_args(2, ciphertext, mac_str);
290
+ (void)self;
291
+ }
292
+
293
+ /**
294
+ * Decrypts data using AES-128-GCM AEAD.
295
+ *
296
+ * @param data [String] Ciphertext to decrypt
297
+ * @param mac: [String] 16-byte authentication tag
298
+ * @param key: [String] 16-byte encryption key
299
+ * @param nonce: [String] 12-byte nonce
300
+ * @param ad: [String, nil] Optional additional authenticated data
301
+ * @return [String] Decrypted plaintext
302
+ * @raise [RuntimeError] if authentication fails
303
+ */
304
+ FIO_SFUNC VALUE iodine_crypto_aes128gcm_decrypt(int argc,
305
+ VALUE *argv,
306
+ VALUE self) {
307
+ fio_buf_info_s data = FIO_BUF_INFO0;
308
+ fio_buf_info_s mac = FIO_BUF_INFO0;
309
+ fio_buf_info_s key = FIO_BUF_INFO0;
310
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
311
+ fio_buf_info_s ad = FIO_BUF_INFO0;
312
+
313
+ iodine_rb2c_arg(argc,
314
+ argv,
315
+ IODINE_ARG_BUF(data, 0, "data", 1),
316
+ IODINE_ARG_BUF(mac, 0, "mac", 1),
317
+ IODINE_ARG_BUF(key, 0, "key", 1),
318
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
319
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
320
+
321
+ if (key.len != 16)
322
+ rb_raise(rb_eArgError, "key must be 16 bytes (got %zu)", key.len);
323
+ if (nonce.len != 12)
324
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
325
+ if (mac.len != 16)
326
+ rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
327
+
328
+ /* Copy data for in-place decryption, also copy mac since it's modified */
329
+ VALUE plaintext = rb_str_new(data.buf, data.len);
330
+ uint8_t mac_copy[16];
331
+ FIO_MEMCPY(mac_copy, mac.buf, 16);
332
+
333
+ int result = fio_aes128_gcm_dec(mac_copy,
334
+ RSTRING_PTR(plaintext),
335
+ data.len,
336
+ ad.buf,
337
+ ad.len,
338
+ (void *)key.buf,
339
+ (void *)nonce.buf);
340
+
341
+ if (result != 0)
342
+ rb_raise(rb_eRuntimeError, "Authentication failed");
343
+
344
+ return plaintext;
345
+ (void)self;
346
+ }
347
+
348
+ /* *****************************************************************************
349
+ AES-256-GCM AEAD Encryption
350
+ ***************************************************************************** */
351
+
352
+ /**
353
+ * Encrypts data using AES-256-GCM AEAD.
354
+ *
355
+ * @param data [String] Plaintext to encrypt
356
+ * @param key: [String] 32-byte encryption key
357
+ * @param nonce: [String] 12-byte nonce (must be unique per key)
358
+ * @param ad: [String, nil] Optional additional authenticated data
359
+ * @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
360
+ */
361
+ FIO_SFUNC VALUE iodine_crypto_aes256gcm_encrypt(int argc,
362
+ VALUE *argv,
363
+ VALUE self) {
364
+ fio_buf_info_s data = FIO_BUF_INFO0;
365
+ fio_buf_info_s key = FIO_BUF_INFO0;
366
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
367
+ fio_buf_info_s ad = FIO_BUF_INFO0;
368
+
369
+ iodine_rb2c_arg(argc,
370
+ argv,
371
+ IODINE_ARG_BUF(data, 0, "data", 1),
372
+ IODINE_ARG_BUF(key, 0, "key", 1),
373
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
374
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
375
+
376
+ if (key.len != 32)
377
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
378
+ if (nonce.len != 12)
379
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
380
+
381
+ /* Copy data for in-place encryption */
382
+ VALUE ciphertext = rb_str_new(data.buf, data.len);
383
+ uint8_t mac[16];
384
+
385
+ fio_aes256_gcm_enc(mac,
386
+ RSTRING_PTR(ciphertext),
387
+ data.len,
388
+ ad.buf,
389
+ ad.len,
390
+ (void *)key.buf,
391
+ (void *)nonce.buf);
392
+
393
+ VALUE mac_str = rb_str_new((const char *)mac, 16);
394
+ return rb_ary_new_from_args(2, ciphertext, mac_str);
395
+ (void)self;
396
+ }
397
+
398
+ /**
399
+ * Decrypts data using AES-256-GCM AEAD.
400
+ *
401
+ * @param data [String] Ciphertext to decrypt
402
+ * @param mac: [String] 16-byte authentication tag
403
+ * @param key: [String] 32-byte encryption key
404
+ * @param nonce: [String] 12-byte nonce
405
+ * @param ad: [String, nil] Optional additional authenticated data
406
+ * @return [String] Decrypted plaintext
407
+ * @raise [RuntimeError] if authentication fails
408
+ */
409
+ FIO_SFUNC VALUE iodine_crypto_aes256gcm_decrypt(int argc,
410
+ VALUE *argv,
411
+ VALUE self) {
412
+ fio_buf_info_s data = FIO_BUF_INFO0;
413
+ fio_buf_info_s mac = FIO_BUF_INFO0;
414
+ fio_buf_info_s key = FIO_BUF_INFO0;
415
+ fio_buf_info_s nonce = FIO_BUF_INFO0;
416
+ fio_buf_info_s ad = FIO_BUF_INFO0;
417
+
418
+ iodine_rb2c_arg(argc,
419
+ argv,
420
+ IODINE_ARG_BUF(data, 0, "data", 1),
421
+ IODINE_ARG_BUF(mac, 0, "mac", 1),
422
+ IODINE_ARG_BUF(key, 0, "key", 1),
423
+ IODINE_ARG_BUF(nonce, 0, "nonce", 1),
424
+ IODINE_ARG_BUF(ad, 0, "ad", 0));
425
+
426
+ if (key.len != 32)
427
+ rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
428
+ if (nonce.len != 12)
429
+ rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
430
+ if (mac.len != 16)
431
+ rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
432
+
433
+ /* Copy data for in-place decryption, also copy mac since it's modified */
434
+ VALUE plaintext = rb_str_new(data.buf, data.len);
435
+ uint8_t mac_copy[16];
436
+ FIO_MEMCPY(mac_copy, mac.buf, 16);
437
+
438
+ int result = fio_aes256_gcm_dec(mac_copy,
439
+ RSTRING_PTR(plaintext),
440
+ data.len,
441
+ ad.buf,
442
+ ad.len,
443
+ (void *)key.buf,
444
+ (void *)nonce.buf);
445
+
446
+ if (result != 0)
447
+ rb_raise(rb_eRuntimeError, "Authentication failed");
448
+
449
+ return plaintext;
450
+ (void)self;
451
+ }
452
+
453
+ /* *****************************************************************************
454
+ Ed25519 Digital Signatures
455
+ ***************************************************************************** */
456
+
457
+ /**
458
+ * Generates a new Ed25519 key pair.
459
+ *
460
+ * @return [Array<String, String>] [secret_key, public_key] both 32 bytes
461
+ */
462
+ FIO_SFUNC VALUE iodine_crypto_ed25519_keypair(VALUE self) {
463
+ uint8_t sk[32], pk[32];
464
+ fio_ed25519_keypair(sk, pk);
465
+ VALUE secret = rb_str_new((const char *)sk, 32);
466
+ VALUE public = rb_str_new((const char *)pk, 32);
467
+ /* Clear secret key from stack */
468
+ fio_memset(sk, 0, 32);
469
+ return rb_ary_new_from_args(2, secret, public);
470
+ (void)self;
471
+ }
472
+
473
+ /**
474
+ * Derives the public key from an Ed25519 secret key.
475
+ *
476
+ * @param secret_key: [String] 32-byte secret key
477
+ * @return [String] 32-byte public key
478
+ */
479
+ FIO_SFUNC VALUE iodine_crypto_ed25519_public_key(int argc,
480
+ VALUE *argv,
481
+ VALUE self) {
482
+ fio_buf_info_s sk = FIO_BUF_INFO0;
483
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(sk, 0, "secret_key", 1));
484
+
485
+ if (sk.len != 32)
486
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
487
+
488
+ uint8_t pk[32];
489
+ fio_ed25519_public_key(pk, (const uint8_t *)sk.buf);
490
+ return rb_str_new((const char *)pk, 32);
491
+ (void)self;
492
+ }
493
+
494
+ /**
495
+ * Signs a message using Ed25519.
496
+ *
497
+ * @param data [String] Message to sign
498
+ * @param secret_key: [String] 32-byte secret key
499
+ * @param public_key: [String] 32-byte public key
500
+ * @return [String] 64-byte signature
501
+ */
502
+ FIO_SFUNC VALUE iodine_crypto_ed25519_sign(int argc, VALUE *argv, VALUE self) {
503
+ fio_buf_info_s data = FIO_BUF_INFO0;
504
+ fio_buf_info_s sk = FIO_BUF_INFO0;
505
+ fio_buf_info_s pk = FIO_BUF_INFO0;
506
+
507
+ iodine_rb2c_arg(argc,
508
+ argv,
509
+ IODINE_ARG_BUF(data, 0, "data", 1),
510
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1),
511
+ IODINE_ARG_BUF(pk, 0, "public_key", 1));
512
+
513
+ if (sk.len != 32)
514
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
515
+ if (pk.len != 32)
516
+ rb_raise(rb_eArgError, "public_key must be 32 bytes (got %zu)", pk.len);
517
+
518
+ uint8_t sig[64];
519
+ fio_ed25519_sign(sig,
520
+ data.buf,
521
+ data.len,
522
+ (const uint8_t *)sk.buf,
523
+ (const uint8_t *)pk.buf);
524
+ return rb_str_new((const char *)sig, 64);
525
+ (void)self;
526
+ }
527
+
528
+ /**
529
+ * Verifies an Ed25519 signature.
530
+ *
531
+ * @param data [String] Original message
532
+ * @param signature [String] 64-byte signature
533
+ * @param public_key: [String] 32-byte public key
534
+ * @return [Boolean] true if valid, false otherwise
535
+ */
536
+ FIO_SFUNC VALUE iodine_crypto_ed25519_verify(int argc,
537
+ VALUE *argv,
538
+ VALUE self) {
539
+ fio_buf_info_s sig = FIO_BUF_INFO0;
540
+ fio_buf_info_s data = FIO_BUF_INFO0;
541
+ fio_buf_info_s pk = FIO_BUF_INFO0;
542
+
543
+ iodine_rb2c_arg(argc,
544
+ argv,
545
+ IODINE_ARG_BUF(data, 0, "data", 1),
546
+ IODINE_ARG_BUF(sig, 0, "signature", 1),
547
+ IODINE_ARG_BUF(pk, 0, "public_key", 1));
548
+
549
+ if (sig.len != 64)
550
+ rb_raise(rb_eArgError, "signature must be 64 bytes (got %zu)", sig.len);
551
+ if (pk.len != 32)
552
+ rb_raise(rb_eArgError, "public_key must be 32 bytes (got %zu)", pk.len);
553
+
554
+ int result = fio_ed25519_verify((const uint8_t *)sig.buf,
555
+ data.buf,
556
+ data.len,
557
+ (const uint8_t *)pk.buf);
558
+ return result == 0 ? Qtrue : Qfalse;
559
+ (void)self;
560
+ }
561
+
562
+ /**
563
+ * Converts an Ed25519 secret key to an X25519 secret key.
564
+ *
565
+ * This allows using an Ed25519 signing key for X25519 key exchange.
566
+ *
567
+ * @param ed_secret_key: [String] 32-byte Ed25519 secret key
568
+ * @return [String] 32-byte X25519 secret key
569
+ */
570
+ FIO_SFUNC VALUE iodine_crypto_ed25519_to_x25519_secret(int argc,
571
+ VALUE *argv,
572
+ VALUE self) {
573
+ fio_buf_info_s ed_sk = FIO_BUF_INFO0;
574
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(ed_sk, 0, "ed_secret_key", 1));
575
+
576
+ if (ed_sk.len != 32)
577
+ rb_raise(rb_eArgError,
578
+ "ed_secret_key must be 32 bytes (got %zu)",
579
+ ed_sk.len);
580
+
581
+ uint8_t x_sk[32];
582
+ fio_ed25519_sk_to_x25519(x_sk, (const uint8_t *)ed_sk.buf);
583
+ VALUE result = rb_str_new((const char *)x_sk, 32);
584
+ /* Clear secret key from stack */
585
+ fio_memset(x_sk, 0, 32);
586
+ return result;
587
+ (void)self;
588
+ }
589
+
590
+ /**
591
+ * Converts an Ed25519 public key to an X25519 public key.
592
+ *
593
+ * This allows encrypting to someone who has only shared their Ed25519
594
+ * signing public key.
595
+ *
596
+ * @param ed_public_key: [String] 32-byte Ed25519 public key
597
+ * @return [String] 32-byte X25519 public key
598
+ */
599
+ FIO_SFUNC VALUE iodine_crypto_ed25519_to_x25519_public(int argc,
600
+ VALUE *argv,
601
+ VALUE self) {
602
+ fio_buf_info_s ed_pk = FIO_BUF_INFO0;
603
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(ed_pk, 0, "ed_public_key", 1));
604
+
605
+ if (ed_pk.len != 32)
606
+ rb_raise(rb_eArgError,
607
+ "ed_public_key must be 32 bytes (got %zu)",
608
+ ed_pk.len);
609
+
610
+ uint8_t x_pk[32];
611
+ fio_ed25519_pk_to_x25519(x_pk, (const uint8_t *)ed_pk.buf);
612
+ return rb_str_new((const char *)x_pk, 32);
613
+ (void)self;
614
+ }
615
+
616
+ /* *****************************************************************************
617
+ X25519 Key Exchange
618
+ ***************************************************************************** */
619
+
620
+ /**
621
+ * Generates a new X25519 key pair.
622
+ *
623
+ * @return [Array<String, String>] [secret_key, public_key] both 32 bytes
624
+ */
625
+ FIO_SFUNC VALUE iodine_crypto_x25519_keypair(VALUE self) {
626
+ uint8_t sk[32], pk[32];
627
+ fio_x25519_keypair(sk, pk);
628
+ VALUE secret = rb_str_new((const char *)sk, 32);
629
+ VALUE public = rb_str_new((const char *)pk, 32);
630
+ /* Clear secret key from stack */
631
+ fio_memset(sk, 0, 32);
632
+ return rb_ary_new_from_args(2, secret, public);
633
+ (void)self;
634
+ }
635
+
636
+ /**
637
+ * Derives the public key from an X25519 secret key.
638
+ *
639
+ * @param secret_key: [String] 32-byte secret key
640
+ * @return [String] 32-byte public key
641
+ */
642
+ FIO_SFUNC VALUE iodine_crypto_x25519_public_key(int argc,
643
+ VALUE *argv,
644
+ VALUE self) {
645
+ fio_buf_info_s sk = FIO_BUF_INFO0;
646
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(sk, 0, "secret_key", 1));
647
+
648
+ if (sk.len != 32)
649
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
650
+
651
+ uint8_t pk[32];
652
+ fio_x25519_public_key(pk, (const uint8_t *)sk.buf);
653
+ return rb_str_new((const char *)pk, 32);
654
+ (void)self;
655
+ }
656
+
657
+ /**
658
+ * Computes a shared secret using X25519 (ECDH).
659
+ *
660
+ * Both parties compute the same shared secret:
661
+ * shared = X25519(my_secret, their_public)
662
+ *
663
+ * @param secret_key: [String] 32-byte own secret key
664
+ * @param their_public: [String] 32-byte other party's public key
665
+ * @return [String] 32-byte shared secret
666
+ * @raise [RuntimeError] if key exchange fails (e.g., low-order point)
667
+ */
668
+ FIO_SFUNC VALUE iodine_crypto_x25519_shared_secret(int argc,
669
+ VALUE *argv,
670
+ VALUE self) {
671
+ fio_buf_info_s sk = FIO_BUF_INFO0;
672
+ fio_buf_info_s their_pk = FIO_BUF_INFO0;
673
+
674
+ iodine_rb2c_arg(argc,
675
+ argv,
676
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1),
677
+ IODINE_ARG_BUF(their_pk, 0, "their_public", 1));
678
+
679
+ if (sk.len != 32)
680
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
681
+ if (their_pk.len != 32)
682
+ rb_raise(rb_eArgError,
683
+ "their_public must be 32 bytes (got %zu)",
684
+ their_pk.len);
685
+
686
+ uint8_t shared[32];
687
+ int result = fio_x25519_shared_secret(shared,
688
+ (const uint8_t *)sk.buf,
689
+ (const uint8_t *)their_pk.buf);
690
+ if (result != 0)
691
+ rb_raise(rb_eRuntimeError, "Key exchange failed (invalid public key)");
692
+
693
+ return rb_str_new((const char *)shared, 32);
694
+ (void)self;
695
+ }
696
+
697
+ /**
698
+ * Encrypts a message using X25519 public-key encryption (ECIES).
699
+ *
700
+ * Uses ephemeral key agreement + ChaCha20-Poly1305 for authenticated
701
+ * encryption. Only the recipient with the matching secret key can decrypt.
702
+ *
703
+ * @param data [String] Plaintext to encrypt
704
+ * @param recipient_pk: [String] 32-byte recipient's public key
705
+ * @return [String] Ciphertext (message.length + 48 bytes overhead)
706
+ * @raise [RuntimeError] if encryption fails
707
+ */
708
+ FIO_SFUNC VALUE iodine_crypto_x25519_encrypt(int argc,
709
+ VALUE *argv,
710
+ VALUE self) {
711
+ fio_buf_info_s data = FIO_BUF_INFO0;
712
+ fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
713
+
714
+ iodine_rb2c_arg(argc,
715
+ argv,
716
+ IODINE_ARG_BUF(data, 0, "data", 1),
717
+ IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
718
+
719
+ if (recipient_pk.len != 32)
720
+ rb_raise(rb_eArgError,
721
+ "recipient_pk must be 32 bytes (got %zu)",
722
+ recipient_pk.len);
723
+
724
+ /* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
725
+ size_t out_len = data.len + 48;
726
+ VALUE ciphertext = rb_str_buf_new(out_len);
727
+ rb_str_set_len(ciphertext, out_len);
728
+
729
+ int result =
730
+ fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
731
+ data.buf,
732
+ data.len,
733
+ (fio_crypto_enc_fn *)fio_chacha20_poly1305_enc,
734
+ (const uint8_t *)recipient_pk.buf);
735
+
736
+ if (result != 0)
737
+ rb_raise(rb_eRuntimeError, "Encryption failed");
738
+
739
+ return ciphertext;
740
+ (void)self;
741
+ }
742
+
743
+ /**
744
+ * Decrypts a message using X25519 public-key encryption (ECIES).
745
+ *
746
+ * @param data [String] Ciphertext from X25519.encrypt
747
+ * @param secret_key: [String] 32-byte recipient's secret key
748
+ * @return [String] Decrypted plaintext
749
+ * @raise [RuntimeError] if decryption fails (authentication error)
750
+ */
751
+ FIO_SFUNC VALUE iodine_crypto_x25519_decrypt(int argc,
752
+ VALUE *argv,
753
+ VALUE self) {
754
+ fio_buf_info_s data = FIO_BUF_INFO0;
755
+ fio_buf_info_s sk = FIO_BUF_INFO0;
756
+
757
+ iodine_rb2c_arg(argc,
758
+ argv,
759
+ IODINE_ARG_BUF(data, 0, "data", 1),
760
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1));
761
+
762
+ if (sk.len != 32)
763
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
764
+ if (data.len < 48)
765
+ rb_raise(rb_eArgError,
766
+ "data too short (minimum 48 bytes, got %zu)",
767
+ data.len);
768
+
769
+ size_t out_len = data.len - 48;
770
+ VALUE plaintext = rb_str_buf_new(out_len);
771
+ rb_str_set_len(plaintext, out_len);
772
+
773
+ int result =
774
+ fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
775
+ (const uint8_t *)data.buf,
776
+ data.len,
777
+ (fio_crypto_dec_fn *)fio_chacha20_poly1305_dec,
778
+ (const uint8_t *)sk.buf);
779
+
780
+ if (result != 0)
781
+ rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
782
+
783
+ return plaintext;
784
+ (void)self;
785
+ }
786
+
787
+ /**
788
+ * Encrypts a message using X25519 public-key encryption (ECIES) with
789
+ * AES-128-GCM.
790
+ *
791
+ * Uses ephemeral key agreement + AES-128-GCM for authenticated encryption.
792
+ * Only the recipient with the matching secret key can decrypt.
793
+ *
794
+ * @param data [String] Plaintext to encrypt
795
+ * @param recipient_pk: [String] 32-byte recipient's public key
796
+ * @return [String] Ciphertext (message.length + 48 bytes overhead)
797
+ * @raise [RuntimeError] if encryption fails
798
+ */
799
+ FIO_SFUNC VALUE iodine_crypto_x25519_encrypt_aes128(int argc,
800
+ VALUE *argv,
801
+ VALUE self) {
802
+ fio_buf_info_s data = FIO_BUF_INFO0;
803
+ fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
804
+
805
+ iodine_rb2c_arg(argc,
806
+ argv,
807
+ IODINE_ARG_BUF(data, 0, "data", 1),
808
+ IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
809
+
810
+ if (recipient_pk.len != 32)
811
+ rb_raise(rb_eArgError,
812
+ "recipient_pk must be 32 bytes (got %zu)",
813
+ recipient_pk.len);
814
+
815
+ /* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
816
+ size_t out_len = data.len + 48;
817
+ VALUE ciphertext = rb_str_buf_new(out_len);
818
+ rb_str_set_len(ciphertext, out_len);
819
+
820
+ int result = fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
821
+ data.buf,
822
+ data.len,
823
+ (fio_crypto_enc_fn *)fio_aes128_gcm_enc,
824
+ (const uint8_t *)recipient_pk.buf);
825
+
826
+ if (result != 0)
827
+ rb_raise(rb_eRuntimeError, "Encryption failed");
828
+
829
+ return ciphertext;
830
+ (void)self;
831
+ }
832
+
833
+ /**
834
+ * Decrypts a message using X25519 public-key encryption (ECIES) with
835
+ * AES-128-GCM.
836
+ *
837
+ * @param data [String] Ciphertext from X25519.encrypt_aes128
838
+ * @param secret_key: [String] 32-byte recipient's secret key
839
+ * @return [String] Decrypted plaintext
840
+ * @raise [RuntimeError] if decryption fails (authentication error)
841
+ */
842
+ FIO_SFUNC VALUE iodine_crypto_x25519_decrypt_aes128(int argc,
843
+ VALUE *argv,
844
+ VALUE self) {
845
+ fio_buf_info_s data = FIO_BUF_INFO0;
846
+ fio_buf_info_s sk = FIO_BUF_INFO0;
847
+
848
+ iodine_rb2c_arg(argc,
849
+ argv,
850
+ IODINE_ARG_BUF(data, 0, "data", 1),
851
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1));
852
+
853
+ if (sk.len != 32)
854
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
855
+ if (data.len < 48)
856
+ rb_raise(rb_eArgError,
857
+ "data too short (minimum 48 bytes, got %zu)",
858
+ data.len);
859
+
860
+ size_t out_len = data.len - 48;
861
+ VALUE plaintext = rb_str_buf_new(out_len);
862
+ rb_str_set_len(plaintext, out_len);
863
+
864
+ int result = fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
865
+ (const uint8_t *)data.buf,
866
+ data.len,
867
+ (fio_crypto_dec_fn *)fio_aes128_gcm_dec,
868
+ (const uint8_t *)sk.buf);
869
+
870
+ if (result != 0)
871
+ rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
872
+
873
+ return plaintext;
874
+ (void)self;
875
+ }
876
+
877
+ /**
878
+ * Encrypts a message using X25519 public-key encryption (ECIES) with
879
+ * AES-256-GCM.
880
+ *
881
+ * Uses ephemeral key agreement + AES-256-GCM for authenticated encryption.
882
+ * Only the recipient with the matching secret key can decrypt.
883
+ *
884
+ * @param data [String] Plaintext to encrypt
885
+ * @param recipient_pk: [String] 32-byte recipient's public key
886
+ * @return [String] Ciphertext (message.length + 48 bytes overhead)
887
+ * @raise [RuntimeError] if encryption fails
888
+ */
889
+ FIO_SFUNC VALUE iodine_crypto_x25519_encrypt_aes256(int argc,
890
+ VALUE *argv,
891
+ VALUE self) {
892
+ fio_buf_info_s data = FIO_BUF_INFO0;
893
+ fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
894
+
895
+ iodine_rb2c_arg(argc,
896
+ argv,
897
+ IODINE_ARG_BUF(data, 0, "data", 1),
898
+ IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
899
+
900
+ if (recipient_pk.len != 32)
901
+ rb_raise(rb_eArgError,
902
+ "recipient_pk must be 32 bytes (got %zu)",
903
+ recipient_pk.len);
904
+
905
+ /* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
906
+ size_t out_len = data.len + 48;
907
+ VALUE ciphertext = rb_str_buf_new(out_len);
908
+ rb_str_set_len(ciphertext, out_len);
909
+
910
+ int result = fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
911
+ data.buf,
912
+ data.len,
913
+ (fio_crypto_enc_fn *)fio_aes256_gcm_enc,
914
+ (const uint8_t *)recipient_pk.buf);
915
+
916
+ if (result != 0)
917
+ rb_raise(rb_eRuntimeError, "Encryption failed");
918
+
919
+ return ciphertext;
920
+ (void)self;
921
+ }
922
+
923
+ /**
924
+ * Decrypts a message using X25519 public-key encryption (ECIES) with
925
+ * AES-256-GCM.
926
+ *
927
+ * @param data [String] Ciphertext from X25519.encrypt_aes256
928
+ * @param secret_key: [String] 32-byte recipient's secret key
929
+ * @return [String] Decrypted plaintext
930
+ * @raise [RuntimeError] if decryption fails (authentication error)
931
+ */
932
+ FIO_SFUNC VALUE iodine_crypto_x25519_decrypt_aes256(int argc,
933
+ VALUE *argv,
934
+ VALUE self) {
935
+ fio_buf_info_s data = FIO_BUF_INFO0;
936
+ fio_buf_info_s sk = FIO_BUF_INFO0;
937
+
938
+ iodine_rb2c_arg(argc,
939
+ argv,
940
+ IODINE_ARG_BUF(data, 0, "data", 1),
941
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1));
942
+
943
+ if (sk.len != 32)
944
+ rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
945
+ if (data.len < 48)
946
+ rb_raise(rb_eArgError,
947
+ "data too short (minimum 48 bytes, got %zu)",
948
+ data.len);
949
+
950
+ size_t out_len = data.len - 48;
951
+ VALUE plaintext = rb_str_buf_new(out_len);
952
+ rb_str_set_len(plaintext, out_len);
953
+
954
+ int result = fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
955
+ (const uint8_t *)data.buf,
956
+ data.len,
957
+ (fio_crypto_dec_fn *)fio_aes256_gcm_dec,
958
+ (const uint8_t *)sk.buf);
959
+
960
+ if (result != 0)
961
+ rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
962
+
963
+ return plaintext;
964
+ (void)self;
965
+ }
966
+
967
+ /* *****************************************************************************
968
+ HKDF Key Derivation (RFC 5869)
969
+ ***************************************************************************** */
970
+
971
+ /**
972
+ * Derives keying material using HKDF (RFC 5869).
973
+ *
974
+ * @param ikm: [String] Input keying material
975
+ * @param salt: [String, nil] Optional salt (random value)
976
+ * @param info: [String, nil] Optional context/application info
977
+ * @param length: [Integer] Desired output length (default: 32)
978
+ * @param sha384: [Boolean] Use SHA-384 instead of SHA-256 (default: false)
979
+ * @return [String] Derived key material
980
+ */
981
+ FIO_SFUNC VALUE iodine_crypto_hkdf_derive(int argc, VALUE *argv, VALUE self) {
982
+ fio_buf_info_s ikm = FIO_BUF_INFO0;
983
+ fio_buf_info_s salt = FIO_BUF_INFO0;
984
+ fio_buf_info_s info = FIO_BUF_INFO0;
985
+ int64_t length = 32;
986
+ uint8_t sha384 = 0;
987
+
988
+ iodine_rb2c_arg(argc,
989
+ argv,
990
+ IODINE_ARG_BUF(ikm, 0, "ikm", 1),
991
+ IODINE_ARG_BUF(salt, 0, "salt", 0),
992
+ IODINE_ARG_BUF(info, 0, "info", 0),
993
+ IODINE_ARG_NUM(length, 0, "length", 0),
994
+ IODINE_ARG_BOOL(sha384, 0, "sha384", 0));
995
+
996
+ /* Max output: 255 * hash_len (32 for SHA-256, 48 for SHA-384) */
997
+ size_t max_len = sha384 ? (size_t)(48 * 255) : (size_t)(32 * 255);
998
+ if (length < 1 || (size_t)length > max_len)
999
+ rb_raise(rb_eArgError,
1000
+ "length must be between 1 and %zu (got %" PRId64 ")",
1001
+ max_len,
1002
+ length);
1003
+
1004
+ VALUE okm = rb_str_buf_new(length);
1005
+ rb_str_set_len(okm, length);
1006
+
1007
+ fio_hkdf(RSTRING_PTR(okm),
1008
+ (size_t)length,
1009
+ salt.buf,
1010
+ salt.len,
1011
+ ikm.buf,
1012
+ ikm.len,
1013
+ info.buf,
1014
+ info.len,
1015
+ (int)sha384);
1016
+
1017
+ return okm;
1018
+ (void)self;
1019
+ }
1020
+
1021
+ /* *****************************************************************************
1022
+ X25519MLKEM768 Post-Quantum Hybrid Key Encapsulation
1023
+ ***************************************************************************** */
1024
+
1025
+ /**
1026
+ * Generates a new X25519MLKEM768 key pair.
1027
+ *
1028
+ * X25519MLKEM768 is a post-quantum hybrid KEM combining X25519 (classical)
1029
+ * with ML-KEM-768 (post-quantum). This provides security against both
1030
+ * classical and quantum attacks.
1031
+ *
1032
+ * @return [Array<String, String>] [secret_key, public_key]
1033
+ * - secret_key: 2432 bytes (ML-KEM-768 sk + X25519 sk)
1034
+ * - public_key: 1216 bytes (ML-KEM-768 pk + X25519 pk)
1035
+ */
1036
+ FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_keypair(VALUE self) {
1037
+ uint8_t pk[1216], sk[2432];
1038
+ int result = fio_x25519mlkem768_keypair(pk, sk);
1039
+ if (result != 0)
1040
+ rb_raise(rb_eRuntimeError, "Key generation failed");
1041
+ VALUE secret = rb_enc_str_new((const char *)sk, 2432, IodineBinaryEncoding);
1042
+ /* store to ring cache, a temporary thing */
1043
+ STORE.cache(secret);
1044
+ VALUE public = rb_enc_str_new((const char *)pk, 1216, IodineBinaryEncoding);
1045
+ /* Clear secret key from stack */
1046
+ fio_memset(sk, 0, 2432);
1047
+ return rb_ary_new_from_args(2, secret, public);
1048
+ (void)self;
1049
+ }
1050
+
1051
+ /**
1052
+ * Encapsulates a shared secret using X25519MLKEM768.
1053
+ *
1054
+ * Performs both X25519 key exchange and ML-KEM-768 encapsulation.
1055
+ * The sender uses this with the recipient's public key to generate
1056
+ * a shared secret and encapsulated data.
1057
+ *
1058
+ * @param public_key: [String] 1216-byte recipient's public key
1059
+ * @return [Array<String, String>] [data, shared_secret]
1060
+ * - data: 1120 bytes (ML-KEM-768 ct + X25519 ephemeral pk)
1061
+ * - shared_secret: 64 bytes (ML-KEM-768 ss || X25519 ss)
1062
+ * @raise [RuntimeError] if encapsulation fails
1063
+ */
1064
+ FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_encapsulate(int argc,
1065
+ VALUE *argv,
1066
+ VALUE self) {
1067
+ fio_buf_info_s pk = FIO_BUF_INFO0;
1068
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(pk, 0, "public_key", 1));
1069
+
1070
+ if (pk.len != 1216)
1071
+ rb_raise(rb_eArgError, "public_key must be 1216 bytes (got %zu)", pk.len);
1072
+
1073
+ uint8_t ct[1120], ss[64];
1074
+ int result = fio_x25519mlkem768_encaps(ct, ss, (const uint8_t *)pk.buf);
1075
+ if (result != 0)
1076
+ rb_raise(rb_eRuntimeError, "Encapsulation failed");
1077
+
1078
+ VALUE ciphertext = rb_str_new((const char *)ct, 1120);
1079
+ STORE.cache(ciphertext);
1080
+ VALUE shared_secret = rb_str_new((const char *)ss, 64);
1081
+ STORE.cache(shared_secret);
1082
+ /* Clear shared secret from stack */
1083
+ fio_memset(ss, 0, 64);
1084
+ return rb_ary_new_from_args(2, ciphertext, shared_secret);
1085
+ (void)self;
1086
+ }
1087
+
1088
+ /**
1089
+ * Decapsulates a shared secret using X25519MLKEM768.
1090
+ *
1091
+ * Performs both X25519 shared secret derivation and ML-KEM-768 decapsulation.
1092
+ * The recipient uses this with their secret key and the sender's data
1093
+ * to recover the shared secret.
1094
+ *
1095
+ * @param data: [String] 1120-byte encapsulated data from encapsulate
1096
+ * @param secret_key: [String] 2432-byte recipient's secret key
1097
+ * @return [String] 64-byte shared secret (ML-KEM-768 ss || X25519 ss)
1098
+ * @raise [RuntimeError] if decapsulation fails (e.g., low-order point)
1099
+ */
1100
+ FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_decapsulate(int argc,
1101
+ VALUE *argv,
1102
+ VALUE self) {
1103
+ fio_buf_info_s ct = FIO_BUF_INFO0;
1104
+ fio_buf_info_s sk = FIO_BUF_INFO0;
1105
+ iodine_rb2c_arg(argc,
1106
+ argv,
1107
+ IODINE_ARG_BUF(ct, 0, "data", 1),
1108
+ IODINE_ARG_BUF(sk, 0, "secret_key", 1));
1109
+
1110
+ if (ct.len != 1120)
1111
+ rb_raise(rb_eArgError, "data must be 1120 bytes (got %zu)", ct.len);
1112
+ if (sk.len != 2432)
1113
+ rb_raise(rb_eArgError, "secret_key must be 2432 bytes (got %zu)", sk.len);
1114
+
1115
+ uint8_t ss[64];
1116
+ int result = fio_x25519mlkem768_decaps(ss,
1117
+ (const uint8_t *)ct.buf,
1118
+ (const uint8_t *)sk.buf);
1119
+ if (result != 0)
1120
+ rb_raise(rb_eRuntimeError, "Decapsulation failed (invalid key or data)");
1121
+
1122
+ VALUE shared_secret = rb_str_new((const char *)ss, 64);
1123
+ /* Clear shared secret from stack */
1124
+ fio_memset(ss, 0, 64);
1125
+ return shared_secret;
1126
+ (void)self;
1127
+ }
1128
+
1129
+ /* *****************************************************************************
1130
+ Argon2 Password Hashing (RFC 9106)
1131
+ ***************************************************************************** */
1132
+
1133
+ /**
1134
+ * Hashes a password using Argon2 (d, i, or id).
1135
+ *
1136
+ * @param password [String] The password to hash.
1137
+ * @param salt [String] A unique salt (minimum 8 bytes recommended).
1138
+ * @param t_cost [Integer] Time cost / iterations (default: 3).
1139
+ * @param m_cost [Integer] Memory cost in KiB (default: 65536 = 64 MiB).
1140
+ * @param parallelism [Integer] Parallelism lanes (default: 1).
1141
+ * @param outlen [Integer] Output length in bytes (default: 32).
1142
+ * @param type [Symbol] Argon2 variant: :d, :i, or :id (default: :id).
1143
+ * @param secret [String, nil] Optional secret key (Argon2 secret value K).
1144
+ * @param ad [String, nil] Optional associated data (Argon2 associated data X).
1145
+ * @return [String] Binary hash of the requested length.
1146
+ */
1147
+ FIO_SFUNC VALUE iodine_crypto_argon2_hash(int argc, VALUE *argv, VALUE self) {
1148
+ fio_buf_info_s password = FIO_BUF_INFO0;
1149
+ fio_buf_info_s salt = FIO_BUF_INFO0;
1150
+ fio_buf_info_s secret = FIO_BUF_INFO0;
1151
+ fio_buf_info_s ad = FIO_BUF_INFO0;
1152
+ int64_t t_cost = 3;
1153
+ int64_t m_cost = 64 * 1024;
1154
+ int64_t parallelism = 1;
1155
+ int64_t outlen = 32;
1156
+ VALUE type_rb = Qnil;
1157
+
1158
+ iodine_rb2c_arg(argc,
1159
+ argv,
1160
+ IODINE_ARG_BUF(password, 0, "password", 1),
1161
+ IODINE_ARG_BUF(salt, 0, "salt", 1),
1162
+ IODINE_ARG_BUF(secret, 0, "secret", 0),
1163
+ IODINE_ARG_BUF(ad, 0, "ad", 0),
1164
+ IODINE_ARG_NUM(t_cost, 0, "t_cost", 0),
1165
+ IODINE_ARG_NUM(m_cost, 0, "m_cost", 0),
1166
+ IODINE_ARG_NUM(parallelism, 0, "parallelism", 0),
1167
+ IODINE_ARG_NUM(outlen, 0, "outlen", 0),
1168
+ IODINE_ARG_RB(type_rb, 0, "type", 0));
1169
+
1170
+ if (outlen < 4 || outlen > 0x0FFFFFFF)
1171
+ rb_raise(rb_eArgError, "outlen must be between 4 and 268435455");
1172
+ if (t_cost < 1)
1173
+ rb_raise(rb_eArgError, "t_cost must be >= 1");
1174
+ if (parallelism < 1)
1175
+ rb_raise(rb_eArgError, "parallelism must be >= 1");
1176
+ if (m_cost < 8 * parallelism)
1177
+ rb_raise(rb_eArgError, "m_cost must be >= 8 * parallelism");
1178
+
1179
+ fio_argon2_type_e type = FIO_ARGON2ID;
1180
+ if (type_rb != Qnil) {
1181
+ if (!RB_TYPE_P(type_rb, RUBY_T_SYMBOL))
1182
+ rb_raise(rb_eTypeError, "type must be a Symbol (:d, :i, or :id)");
1183
+ ID type_id = rb_sym2id(type_rb);
1184
+ if (type_id == rb_intern("d"))
1185
+ type = FIO_ARGON2D;
1186
+ else if (type_id == rb_intern("i"))
1187
+ type = FIO_ARGON2I;
1188
+ else if (type_id == rb_intern("id"))
1189
+ type = FIO_ARGON2ID;
1190
+ else
1191
+ rb_raise(rb_eArgError, "type must be :d, :i, or :id");
1192
+ }
1193
+
1194
+ VALUE out = rb_str_buf_new((long)outlen);
1195
+ rb_str_set_len(out, (long)outlen);
1196
+
1197
+ int result = fio_argon2_hash(RSTRING_PTR(out),
1198
+ .password = password,
1199
+ .salt = salt,
1200
+ .secret = secret,
1201
+ .ad = ad,
1202
+ .t_cost = (uint32_t)t_cost,
1203
+ .m_cost = (uint32_t)m_cost,
1204
+ .parallelism = (uint32_t)parallelism,
1205
+ .outlen = (uint32_t)outlen,
1206
+ .type = type);
1207
+ if (result != 0)
1208
+ rb_raise(rb_eRuntimeError, "Argon2 hashing failed");
1209
+
1210
+ return out;
1211
+ (void)self;
1212
+ }
1213
+
1214
+ /* *****************************************************************************
1215
+ Lyra2 Password Hashing
1216
+ ***************************************************************************** */
1217
+
1218
+ /**
1219
+ * Hashes a password using Lyra2.
1220
+ *
1221
+ * @param password [String] The password to hash.
1222
+ * @param salt [String] A unique salt.
1223
+ * @param t_cost [Integer] Time cost / rounds (default: 1).
1224
+ * @param m_cost [Integer] Memory cost / rows in matrix (default: 1000).
1225
+ * @param outlen [Integer] Output length in bytes (default: 32).
1226
+ * @param n_cols [Integer] Number of columns (default: 256).
1227
+ * @return [String] Binary hash of the requested length.
1228
+ */
1229
+ FIO_SFUNC VALUE iodine_crypto_lyra2_hash(int argc, VALUE *argv, VALUE self) {
1230
+ fio_buf_info_s password = FIO_BUF_INFO0;
1231
+ fio_buf_info_s salt = FIO_BUF_INFO0;
1232
+ int64_t t_cost = 1;
1233
+ int64_t m_cost = 1000;
1234
+ int64_t outlen = 32;
1235
+ int64_t n_cols = 256;
1236
+
1237
+ iodine_rb2c_arg(argc,
1238
+ argv,
1239
+ IODINE_ARG_BUF(password, 0, "password", 1),
1240
+ IODINE_ARG_BUF(salt, 0, "salt", 1),
1241
+ IODINE_ARG_NUM(t_cost, 0, "t_cost", 0),
1242
+ IODINE_ARG_NUM(m_cost, 0, "m_cost", 0),
1243
+ IODINE_ARG_NUM(outlen, 0, "outlen", 0),
1244
+ IODINE_ARG_NUM(n_cols, 0, "n_cols", 0));
1245
+
1246
+ if (outlen < 1 || outlen > 0x0FFFFFFF)
1247
+ rb_raise(rb_eArgError, "outlen must be between 1 and 268435455");
1248
+ if (t_cost < 1)
1249
+ rb_raise(rb_eArgError, "t_cost must be >= 1");
1250
+ if (m_cost < 3)
1251
+ rb_raise(rb_eArgError, "m_cost must be >= 3");
1252
+ if (n_cols < 1)
1253
+ rb_raise(rb_eArgError, "n_cols must be >= 1");
1254
+
1255
+ VALUE out = rb_str_buf_new((long)outlen);
1256
+ rb_str_set_len(out, (long)outlen);
1257
+
1258
+ int result = fio_lyra2_hash(RSTRING_PTR(out),
1259
+ .password = password,
1260
+ .salt = salt,
1261
+ .t_cost = (uint64_t)t_cost,
1262
+ .m_cost = (uint64_t)m_cost,
1263
+ .outlen = (size_t)outlen,
1264
+ .n_cols = (size_t)n_cols);
1265
+ if (result != 0)
1266
+ rb_raise(rb_eRuntimeError, "Lyra2 hashing failed");
1267
+
1268
+ return out;
1269
+ (void)self;
1270
+ }
1271
+
1272
+ /* *****************************************************************************
1273
+ Module Initialization
1274
+ ***************************************************************************** */
1275
+
1276
+ static void Init_Iodine_Crypto(void) {
1277
+ /* Iodine::Base::Crypto */
1278
+ iodine_rb_CRYPTO = rb_define_module_under(iodine_rb_IODINE_BASE, "Crypto");
1279
+ STORE.hold(iodine_rb_CRYPTO);
1280
+
1281
+ /* Iodine::Base::Crypto::ChaCha20Poly1305 */
1282
+ iodine_rb_CHACHA20POLY1305 =
1283
+ rb_define_module_under(iodine_rb_CRYPTO, "ChaCha20Poly1305");
1284
+ STORE.hold(iodine_rb_CHACHA20POLY1305);
1285
+ rb_define_module_function(iodine_rb_CHACHA20POLY1305,
1286
+ "encrypt",
1287
+ iodine_crypto_chacha_encrypt,
1288
+ -1);
1289
+ rb_define_module_function(iodine_rb_CHACHA20POLY1305,
1290
+ "decrypt",
1291
+ iodine_crypto_chacha_decrypt,
1292
+ -1);
1293
+
1294
+ /* Iodine::Base::Crypto::XChaCha20Poly1305 */
1295
+ iodine_rb_XCHACHA20POLY1305 =
1296
+ rb_define_module_under(iodine_rb_CRYPTO, "XChaCha20Poly1305");
1297
+ STORE.hold(iodine_rb_XCHACHA20POLY1305);
1298
+ rb_define_module_function(iodine_rb_XCHACHA20POLY1305,
1299
+ "encrypt",
1300
+ iodine_crypto_xchacha_encrypt,
1301
+ -1);
1302
+ rb_define_module_function(iodine_rb_XCHACHA20POLY1305,
1303
+ "decrypt",
1304
+ iodine_crypto_xchacha_decrypt,
1305
+ -1);
1306
+
1307
+ /* Iodine::Base::Crypto::AES128GCM */
1308
+ iodine_rb_AES128GCM = rb_define_module_under(iodine_rb_CRYPTO, "AES128GCM");
1309
+ STORE.hold(iodine_rb_AES128GCM);
1310
+ rb_define_module_function(iodine_rb_AES128GCM,
1311
+ "encrypt",
1312
+ iodine_crypto_aes128gcm_encrypt,
1313
+ -1);
1314
+ rb_define_module_function(iodine_rb_AES128GCM,
1315
+ "decrypt",
1316
+ iodine_crypto_aes128gcm_decrypt,
1317
+ -1);
1318
+
1319
+ /* Iodine::Base::Crypto::AES256GCM */
1320
+ iodine_rb_AES256GCM = rb_define_module_under(iodine_rb_CRYPTO, "AES256GCM");
1321
+ STORE.hold(iodine_rb_AES256GCM);
1322
+ rb_define_module_function(iodine_rb_AES256GCM,
1323
+ "encrypt",
1324
+ iodine_crypto_aes256gcm_encrypt,
1325
+ -1);
1326
+ rb_define_module_function(iodine_rb_AES256GCM,
1327
+ "decrypt",
1328
+ iodine_crypto_aes256gcm_decrypt,
1329
+ -1);
1330
+
1331
+ /* Iodine::Base::Crypto::Ed25519 */
1332
+ iodine_rb_ED25519 = rb_define_module_under(iodine_rb_CRYPTO, "Ed25519");
1333
+ STORE.hold(iodine_rb_ED25519);
1334
+ rb_define_module_function(iodine_rb_ED25519,
1335
+ "keypair",
1336
+ iodine_crypto_ed25519_keypair,
1337
+ 0);
1338
+ rb_define_module_function(iodine_rb_ED25519,
1339
+ "public_key",
1340
+ iodine_crypto_ed25519_public_key,
1341
+ -1);
1342
+ rb_define_module_function(iodine_rb_ED25519,
1343
+ "sign",
1344
+ iodine_crypto_ed25519_sign,
1345
+ -1);
1346
+ rb_define_module_function(iodine_rb_ED25519,
1347
+ "verify",
1348
+ iodine_crypto_ed25519_verify,
1349
+ -1);
1350
+ rb_define_module_function(iodine_rb_ED25519,
1351
+ "to_x25519_secret",
1352
+ iodine_crypto_ed25519_to_x25519_secret,
1353
+ -1);
1354
+ rb_define_module_function(iodine_rb_ED25519,
1355
+ "to_x25519_public",
1356
+ iodine_crypto_ed25519_to_x25519_public,
1357
+ -1);
1358
+
1359
+ /* Iodine::Base::Crypto::X25519 */
1360
+ iodine_rb_X25519 = rb_define_module_under(iodine_rb_CRYPTO, "X25519");
1361
+ STORE.hold(iodine_rb_X25519);
1362
+ rb_define_module_function(iodine_rb_X25519,
1363
+ "keypair",
1364
+ iodine_crypto_x25519_keypair,
1365
+ 0);
1366
+ rb_define_module_function(iodine_rb_X25519,
1367
+ "public_key",
1368
+ iodine_crypto_x25519_public_key,
1369
+ -1);
1370
+ rb_define_module_function(iodine_rb_X25519,
1371
+ "shared_secret",
1372
+ iodine_crypto_x25519_shared_secret,
1373
+ -1);
1374
+ rb_define_module_function(iodine_rb_X25519,
1375
+ "encrypt",
1376
+ iodine_crypto_x25519_encrypt,
1377
+ -1);
1378
+ rb_define_module_function(iodine_rb_X25519,
1379
+ "decrypt",
1380
+ iodine_crypto_x25519_decrypt,
1381
+ -1);
1382
+ rb_define_module_function(iodine_rb_X25519,
1383
+ "encrypt_aes128",
1384
+ iodine_crypto_x25519_encrypt_aes128,
1385
+ -1);
1386
+ rb_define_module_function(iodine_rb_X25519,
1387
+ "decrypt_aes128",
1388
+ iodine_crypto_x25519_decrypt_aes128,
1389
+ -1);
1390
+ rb_define_module_function(iodine_rb_X25519,
1391
+ "encrypt_aes256",
1392
+ iodine_crypto_x25519_encrypt_aes256,
1393
+ -1);
1394
+ rb_define_module_function(iodine_rb_X25519,
1395
+ "decrypt_aes256",
1396
+ iodine_crypto_x25519_decrypt_aes256,
1397
+ -1);
1398
+
1399
+ /* Iodine::Base::Crypto::HKDF */
1400
+ iodine_rb_HKDF = rb_define_module_under(iodine_rb_CRYPTO, "HKDF");
1401
+ STORE.hold(iodine_rb_HKDF);
1402
+ rb_define_module_function(iodine_rb_HKDF,
1403
+ "derive",
1404
+ iodine_crypto_hkdf_derive,
1405
+ -1);
1406
+
1407
+ /* Iodine::Base::Crypto::X25519MLKEM768 */
1408
+ iodine_rb_X25519MLKEM768 =
1409
+ rb_define_module_under(iodine_rb_CRYPTO, "X25519MLKEM768");
1410
+ STORE.hold(iodine_rb_X25519MLKEM768);
1411
+ rb_define_module_function(iodine_rb_X25519MLKEM768,
1412
+ "keypair",
1413
+ iodine_crypto_x25519mlkem768_keypair,
1414
+ 0);
1415
+ rb_define_module_function(iodine_rb_X25519MLKEM768,
1416
+ "encapsulate",
1417
+ iodine_crypto_x25519mlkem768_encapsulate,
1418
+ -1);
1419
+ rb_define_module_function(iodine_rb_X25519MLKEM768,
1420
+ "decapsulate",
1421
+ iodine_crypto_x25519mlkem768_decapsulate,
1422
+ -1);
1423
+
1424
+ /* Iodine::Base::Crypto::Argon2 */
1425
+ iodine_rb_ARGON2 = rb_define_module_under(iodine_rb_CRYPTO, "Argon2");
1426
+ STORE.hold(iodine_rb_ARGON2);
1427
+ rb_define_module_function(iodine_rb_ARGON2,
1428
+ "hash",
1429
+ iodine_crypto_argon2_hash,
1430
+ -1);
1431
+
1432
+ /* Iodine::Base::Crypto::Lyra2 */
1433
+ iodine_rb_LYRA2 = rb_define_module_under(iodine_rb_CRYPTO, "Lyra2");
1434
+ STORE.hold(iodine_rb_LYRA2);
1435
+ rb_define_module_function(iodine_rb_LYRA2,
1436
+ "hash",
1437
+ iodine_crypto_lyra2_hash,
1438
+ -1);
1439
+ }
1440
+
1441
+ #endif /* H___IODINE_CRYPTO___H */