chacha20blake3 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 884132d1f327e45e947d008367009753b1c014a8809a1330fb1cbc8cf014def6
4
+ data.tar.gz: c1ff6117acd18ead79c48d08a9bcb2f223dd4524e65b93268f06c9ca1ac16b37
5
+ SHA512:
6
+ metadata.gz: 71187d3c2e6d86858938c3fe26476c60bdc1af777d4eee842794575d1823fb95514d73fe14282c1e38828b6b691e2d71ffbf5c570e6abb2cd0efc438a3e9a547
7
+ data.tar.gz: affa5cb7f8123f050a9f8cb38b83f1c11110e7730ee360b53287e81c12c8017a36ffcb68dc88113be539aeab23247626864023b27b39463b6ec48f8a20b8ad84
data/Cargo.lock ADDED
@@ -0,0 +1,402 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.4"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "arrayref"
16
+ version = "0.3.9"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
19
+
20
+ [[package]]
21
+ name = "arrayvec"
22
+ version = "0.7.6"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
25
+ dependencies = [
26
+ "zeroize",
27
+ ]
28
+
29
+ [[package]]
30
+ name = "bindgen"
31
+ version = "0.72.1"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
34
+ dependencies = [
35
+ "bitflags",
36
+ "cexpr",
37
+ "clang-sys",
38
+ "itertools",
39
+ "proc-macro2",
40
+ "quote",
41
+ "regex",
42
+ "rustc-hash",
43
+ "shlex",
44
+ "syn",
45
+ ]
46
+
47
+ [[package]]
48
+ name = "bitflags"
49
+ version = "2.11.0"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
52
+
53
+ [[package]]
54
+ name = "blake3"
55
+ version = "1.8.4"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e"
58
+ dependencies = [
59
+ "arrayref",
60
+ "arrayvec",
61
+ "cc",
62
+ "cfg-if",
63
+ "constant_time_eq",
64
+ "cpufeatures",
65
+ "zeroize",
66
+ ]
67
+
68
+ [[package]]
69
+ name = "cc"
70
+ version = "1.2.59"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
73
+ dependencies = [
74
+ "find-msvc-tools",
75
+ "shlex",
76
+ ]
77
+
78
+ [[package]]
79
+ name = "cexpr"
80
+ version = "0.6.0"
81
+ source = "registry+https://github.com/rust-lang/crates.io-index"
82
+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
83
+ dependencies = [
84
+ "nom",
85
+ ]
86
+
87
+ [[package]]
88
+ name = "cfg-if"
89
+ version = "1.0.4"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
92
+
93
+ [[package]]
94
+ name = "chacha"
95
+ version = "0.1.0"
96
+ source = "git+https://github.com/skerkour/chacha20-blake3?rev=9942bf34c77b03896bb1733079256076ca823e58#9942bf34c77b03896bb1733079256076ca823e58"
97
+ dependencies = [
98
+ "zeroize",
99
+ ]
100
+
101
+ [[package]]
102
+ name = "chacha20-blake3"
103
+ version = "0.9.11"
104
+ source = "git+https://github.com/skerkour/chacha20-blake3?rev=9942bf34c77b03896bb1733079256076ca823e58#9942bf34c77b03896bb1733079256076ca823e58"
105
+ dependencies = [
106
+ "blake3",
107
+ "chacha",
108
+ "constant_time_eq",
109
+ "zeroize",
110
+ ]
111
+
112
+ [[package]]
113
+ name = "chacha20blake3"
114
+ version = "0.1.0"
115
+ dependencies = [
116
+ "blake3",
117
+ "chacha20-blake3",
118
+ "getrandom",
119
+ "magnus",
120
+ "rb-sys",
121
+ ]
122
+
123
+ [[package]]
124
+ name = "clang-sys"
125
+ version = "1.8.1"
126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
127
+ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
128
+ dependencies = [
129
+ "glob",
130
+ "libc",
131
+ "libloading",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "constant_time_eq"
136
+ version = "0.4.2"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
139
+
140
+ [[package]]
141
+ name = "cpufeatures"
142
+ version = "0.3.0"
143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
144
+ checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
145
+ dependencies = [
146
+ "libc",
147
+ ]
148
+
149
+ [[package]]
150
+ name = "either"
151
+ version = "1.15.0"
152
+ source = "registry+https://github.com/rust-lang/crates.io-index"
153
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
154
+
155
+ [[package]]
156
+ name = "find-msvc-tools"
157
+ version = "0.1.9"
158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
159
+ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
160
+
161
+ [[package]]
162
+ name = "getrandom"
163
+ version = "0.2.17"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
166
+ dependencies = [
167
+ "cfg-if",
168
+ "libc",
169
+ "wasi",
170
+ ]
171
+
172
+ [[package]]
173
+ name = "glob"
174
+ version = "0.3.3"
175
+ source = "registry+https://github.com/rust-lang/crates.io-index"
176
+ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
177
+
178
+ [[package]]
179
+ name = "itertools"
180
+ version = "0.13.0"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
183
+ dependencies = [
184
+ "either",
185
+ ]
186
+
187
+ [[package]]
188
+ name = "lazy_static"
189
+ version = "1.5.0"
190
+ source = "registry+https://github.com/rust-lang/crates.io-index"
191
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
192
+
193
+ [[package]]
194
+ name = "libc"
195
+ version = "0.2.184"
196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
197
+ checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
198
+
199
+ [[package]]
200
+ name = "libloading"
201
+ version = "0.8.9"
202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
203
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
204
+ dependencies = [
205
+ "cfg-if",
206
+ "windows-link",
207
+ ]
208
+
209
+ [[package]]
210
+ name = "magnus"
211
+ version = "0.8.2"
212
+ source = "registry+https://github.com/rust-lang/crates.io-index"
213
+ checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
214
+ dependencies = [
215
+ "magnus-macros",
216
+ "rb-sys",
217
+ "rb-sys-env",
218
+ "seq-macro",
219
+ ]
220
+
221
+ [[package]]
222
+ name = "magnus-macros"
223
+ version = "0.8.0"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
226
+ dependencies = [
227
+ "proc-macro2",
228
+ "quote",
229
+ "syn",
230
+ ]
231
+
232
+ [[package]]
233
+ name = "memchr"
234
+ version = "2.8.0"
235
+ source = "registry+https://github.com/rust-lang/crates.io-index"
236
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
237
+
238
+ [[package]]
239
+ name = "minimal-lexical"
240
+ version = "0.2.1"
241
+ source = "registry+https://github.com/rust-lang/crates.io-index"
242
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
243
+
244
+ [[package]]
245
+ name = "nom"
246
+ version = "7.1.3"
247
+ source = "registry+https://github.com/rust-lang/crates.io-index"
248
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
249
+ dependencies = [
250
+ "memchr",
251
+ "minimal-lexical",
252
+ ]
253
+
254
+ [[package]]
255
+ name = "proc-macro2"
256
+ version = "1.0.106"
257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
258
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
259
+ dependencies = [
260
+ "unicode-ident",
261
+ ]
262
+
263
+ [[package]]
264
+ name = "quote"
265
+ version = "1.0.45"
266
+ source = "registry+https://github.com/rust-lang/crates.io-index"
267
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
268
+ dependencies = [
269
+ "proc-macro2",
270
+ ]
271
+
272
+ [[package]]
273
+ name = "rb-sys"
274
+ version = "0.9.126"
275
+ source = "registry+https://github.com/rust-lang/crates.io-index"
276
+ checksum = "284799e73e899fe946fd77c7211b83bff61a1356e039ade7a2516a779e3212d0"
277
+ dependencies = [
278
+ "rb-sys-build",
279
+ ]
280
+
281
+ [[package]]
282
+ name = "rb-sys-build"
283
+ version = "0.9.126"
284
+ source = "registry+https://github.com/rust-lang/crates.io-index"
285
+ checksum = "855fc1ad8943d12c89ef12f9147f1cc531f5bf19fb744112fdd317bb6ee7b5c5"
286
+ dependencies = [
287
+ "bindgen",
288
+ "lazy_static",
289
+ "proc-macro2",
290
+ "quote",
291
+ "regex",
292
+ "shell-words",
293
+ "syn",
294
+ ]
295
+
296
+ [[package]]
297
+ name = "rb-sys-env"
298
+ version = "0.2.3"
299
+ source = "registry+https://github.com/rust-lang/crates.io-index"
300
+ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
301
+
302
+ [[package]]
303
+ name = "regex"
304
+ version = "1.12.3"
305
+ source = "registry+https://github.com/rust-lang/crates.io-index"
306
+ checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
307
+ dependencies = [
308
+ "aho-corasick",
309
+ "memchr",
310
+ "regex-automata",
311
+ "regex-syntax",
312
+ ]
313
+
314
+ [[package]]
315
+ name = "regex-automata"
316
+ version = "0.4.14"
317
+ source = "registry+https://github.com/rust-lang/crates.io-index"
318
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
319
+ dependencies = [
320
+ "aho-corasick",
321
+ "memchr",
322
+ "regex-syntax",
323
+ ]
324
+
325
+ [[package]]
326
+ name = "regex-syntax"
327
+ version = "0.8.10"
328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
329
+ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
330
+
331
+ [[package]]
332
+ name = "rustc-hash"
333
+ version = "2.1.2"
334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
335
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
336
+
337
+ [[package]]
338
+ name = "seq-macro"
339
+ version = "0.3.6"
340
+ source = "registry+https://github.com/rust-lang/crates.io-index"
341
+ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
342
+
343
+ [[package]]
344
+ name = "shell-words"
345
+ version = "1.1.1"
346
+ source = "registry+https://github.com/rust-lang/crates.io-index"
347
+ checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
348
+
349
+ [[package]]
350
+ name = "shlex"
351
+ version = "1.3.0"
352
+ source = "registry+https://github.com/rust-lang/crates.io-index"
353
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
354
+
355
+ [[package]]
356
+ name = "syn"
357
+ version = "2.0.117"
358
+ source = "registry+https://github.com/rust-lang/crates.io-index"
359
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
360
+ dependencies = [
361
+ "proc-macro2",
362
+ "quote",
363
+ "unicode-ident",
364
+ ]
365
+
366
+ [[package]]
367
+ name = "unicode-ident"
368
+ version = "1.0.24"
369
+ source = "registry+https://github.com/rust-lang/crates.io-index"
370
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
371
+
372
+ [[package]]
373
+ name = "wasi"
374
+ version = "0.11.1+wasi-snapshot-preview1"
375
+ source = "registry+https://github.com/rust-lang/crates.io-index"
376
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
377
+
378
+ [[package]]
379
+ name = "windows-link"
380
+ version = "0.2.1"
381
+ source = "registry+https://github.com/rust-lang/crates.io-index"
382
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
383
+
384
+ [[package]]
385
+ name = "zeroize"
386
+ version = "1.8.2"
387
+ source = "registry+https://github.com/rust-lang/crates.io-index"
388
+ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
389
+ dependencies = [
390
+ "zeroize_derive",
391
+ ]
392
+
393
+ [[package]]
394
+ name = "zeroize_derive"
395
+ version = "1.4.3"
396
+ source = "registry+https://github.com/rust-lang/crates.io-index"
397
+ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
398
+ dependencies = [
399
+ "proc-macro2",
400
+ "quote",
401
+ "syn",
402
+ ]
data/Cargo.toml ADDED
@@ -0,0 +1,9 @@
1
+ [workspace]
2
+ members = ["ext/chacha20blake3"]
3
+ resolver = "2"
4
+
5
+ [profile.release]
6
+ opt-level = 3
7
+ lto = true
8
+ codegen-units = 1
9
+ panic = "abort"
data/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # ChaCha20Blake3
2
+
3
+ [![CI](https://github.com/paddor/chacha20blake3/actions/workflows/ci.yml/badge.svg)](https://github.com/paddor/chacha20blake3/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/chacha20blake3?color=e9573f)](https://rubygems.org/gems/chacha20blake3)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ > **Warning:** This gem is not maintained by cryptographers. The author is not a
8
+ > cryptographer. It has not been independently audited. For production use where
9
+ > proven, audited libraries matter, consider [RbNaCl](https://github.com/RubyCrypto/rbnacl)
10
+ > (XChaCha20-Poly1305) or another gem from [RubyCrypto](https://github.com/RubyCrypto).
11
+ >
12
+ > This gem exists because no Ruby binding for ChaCha20-BLAKE3 existed yet. If
13
+ > RbNaCl adds ChaCha20-BLAKE3 support, or this gem is transferred to
14
+ > [RubyCrypto](https://github.com/RubyCrypto), this warning will be removed.
15
+
16
+ Fast, paranoia-grade authenticated encryption for Ruby -- no NIST primitives, no hardware AES requirement.
17
+
18
+ This gem wraps the [`chacha20-blake3`](https://github.com/skerkour/chacha20-blake3) Rust crate via a native Rust extension ([magnus](https://github.com/matsadler/magnus)). The underlying cipher combines:
19
+
20
+ - **ChaCha20** stream cipher (DJB, RFC 8439)
21
+ - **BLAKE3** as the authentication MAC
22
+
23
+ SIMD-accelerated on every major architecture:
24
+ | Platform | Acceleration |
25
+ |----------|-------------|
26
+ | x86-64 | AVX2, AVX-512 |
27
+ | ARM64 | NEON, SVE |
28
+ | Generic | Pure Rust fallback |
29
+
30
+ No AES-NI, no hardware crypto instructions, no NIST curves. Pure DJB crypto all the way down.
31
+
32
+ ## Why ChaCha20 + BLAKE3?
33
+
34
+ - **No NIST influence** - avoids AES (backdoor concerns), P-256/P-384 (nothing-up-my-sleeve suspicion), SHA-2 (NSA design)
35
+ - **Hardware-agnostic** - fast on any CPU, not just ones with AES-NI
36
+ - **Embedded-friendly** - small code size, no hardware requirements
37
+ - **Modern MAC** - BLAKE3 is faster than Poly1305 on larger payloads and provides 256-bit tags vs 128-bit
38
+
39
+ ## Installation
40
+
41
+ Add to your Gemfile:
42
+
43
+ ```ruby
44
+ gem "chacha20blake3"
45
+ ```
46
+
47
+ Or install directly:
48
+
49
+ ```sh
50
+ gem install chacha20blake3
51
+ ```
52
+
53
+ Building from source requires a Rust toolchain (`rustup.rs`).
54
+
55
+ ## Quick Start
56
+
57
+ ```ruby
58
+ require "chacha20blake3"
59
+
60
+ # Generate a random key and nonce
61
+ key = ChaCha20Blake3.generate_key # 32 bytes, CSPRNG
62
+ nonce = ChaCha20Blake3.generate_nonce # 24 bytes, CSPRNG
63
+
64
+ cipher = ChaCha20Blake3::Cipher.new(key)
65
+
66
+ # Encrypt - returns ciphertext with tag appended (last 32 bytes)
67
+ ciphertext = cipher.encrypt(nonce, "Hello, world!")
68
+
69
+ # Decrypt - raises ChaCha20Blake3::DecryptionError on authentication failure
70
+ plaintext = cipher.decrypt(nonce, ciphertext)
71
+ # => "Hello, world!" (Encoding::BINARY)
72
+ ```
73
+
74
+ ### With Associated Data (AAD)
75
+
76
+ AAD is authenticated but not encrypted. Use it to bind ciphertext to context
77
+ (e.g., a session ID, record ID, version header):
78
+
79
+ ```ruby
80
+ aad = "user_id=42,version=1"
81
+
82
+ ct = cipher.encrypt(nonce, plaintext, aad: aad)
83
+ pt = cipher.decrypt(nonce, ct, aad: aad)
84
+
85
+ # Wrong AAD raises DecryptionError
86
+ cipher.decrypt(nonce, ct, aad: "tampered") # => raises!
87
+ ```
88
+
89
+ ### Detached Tag
90
+
91
+ Store the 32-byte authentication tag separately from the ciphertext:
92
+
93
+ ```ruby
94
+ ct, tag = cipher.encrypt_detached(nonce, plaintext, aad: aad)
95
+
96
+ # ct and tag can be stored/transmitted separately
97
+ pt = cipher.decrypt_detached(nonce, ct, tag, aad: aad)
98
+ ```
99
+
100
+ ## API Reference
101
+
102
+ ### Constants
103
+
104
+ | Constant | Value | Description |
105
+ |----------|-------|-------------|
106
+ | `ChaCha20Blake3::KEY_SIZE` | `32` | Key length in bytes |
107
+ | `ChaCha20Blake3::NONCE_SIZE` | `24` | Nonce length in bytes |
108
+ | `ChaCha20Blake3::TAG_SIZE` | `32` | Authentication tag length in bytes |
109
+
110
+ ### Module Methods
111
+
112
+ #### `ChaCha20Blake3.generate_key -> String`
113
+ Returns 32 cryptographically random bytes (BINARY encoding). Uses the OS CSPRNG (`getrandom(2)` on Linux, `BCryptGenRandom` on Windows).
114
+
115
+ #### `ChaCha20Blake3.generate_nonce -> String`
116
+ Returns 24 cryptographically random bytes (BINARY encoding).
117
+
118
+ #### `ChaCha20Blake3.generate_key_and_nonce -> [key, nonce]`
119
+ Convenience wrapper returning `[generate_key, generate_nonce]`.
120
+
121
+ ### `ChaCha20Blake3::Cipher`
122
+
123
+ #### `.new(key) -> Cipher`
124
+ Creates a cipher with the given 32-byte key (BINARY String). Raises `ArgumentError` if the key is not exactly 32 bytes.
125
+
126
+ #### `#encrypt(nonce, plaintext, aad: "") -> String`
127
+ Encrypts `plaintext` and returns a BINARY String containing the ciphertext followed by the 32-byte authentication tag. `nonce` must be exactly 24 bytes.
128
+
129
+ Output length: `plaintext.bytesize + 32`
130
+
131
+ #### `#decrypt(nonce, ciphertext, aad: "") -> String`
132
+ Decrypts and authenticates `ciphertext` (which must include the 32-byte tag). Returns the plaintext as a BINARY String, or raises `ChaCha20Blake3::DecryptionError` if authentication fails.
133
+
134
+ #### `#encrypt_detached(nonce, plaintext, aad: "") -> [ciphertext, tag]`
135
+ Encrypts `plaintext`, returning ciphertext and the 32-byte tag as separate BINARY Strings.
136
+
137
+ #### `#decrypt_detached(nonce, ciphertext, tag, aad: "") -> String`
138
+ Decrypts and authenticates using a separately-provided 32-byte tag. Raises `ChaCha20Blake3::DecryptionError` on failure.
139
+
140
+ ### Thread Safety
141
+
142
+ Both `Cipher` and `Stream` instances are safe to share across threads (and
143
+ Ractors).
144
+
145
+ `Cipher` is stateless (it only holds the key), so concurrent calls are safe
146
+ by nature. The caller is responsible for never reusing a nonce.
147
+
148
+ `Stream` holds an internal counter that determines the next nonce. All
149
+ operations hold a mutex for the duration of encrypt/decrypt, ensuring that no
150
+ two threads can encrypt with the same nonce. This is a security invariant,
151
+ not just a correctness one: nonce reuse in a stream cipher is catastrophic
152
+ (see below).
153
+
154
+ ### `ChaCha20Blake3::DecryptionError < StandardError`
155
+ Raised when authentication verification fails during decryption. **Never** use this as a timing oracle - the verification is constant-time.
156
+
157
+ ## Security Notes
158
+
159
+ ### ⚠️ Don't Roll Your Own Protocol
160
+
161
+ This gem is a raw AEAD cipher, not a secure protocol. If you need encrypted
162
+ messaging, file encryption, or transport security, use a proven protocol
163
+ library (libsodium's secretstream, Noise Framework, TLS) instead of
164
+ combining primitives yourself.
165
+
166
+ ### Nonce Reuse Is Catastrophic
167
+
168
+ **A (key, nonce) pair must NEVER be used to encrypt two different messages.**
169
+
170
+ Nonce reuse in a stream cipher destroys confidentiality: an attacker who sees two ciphertexts encrypted with the same (key, nonce) can XOR them together, cancelling the keystream and recovering the XOR of the two plaintexts. If either plaintext has known structure (and it usually does), both are recoverable.
171
+
172
+ Safe nonce strategies:
173
+ - Use `generate_nonce` for each message (24-byte nonces make accidental collision astronomically unlikely)
174
+ - Use a counter-based nonce with a single long-lived key
175
+ - Derive a fresh key per session with a KDF
176
+
177
+ ### Key Management
178
+
179
+ Keys must be treated as secrets. Use `SecureRandom.bytes(32)` or `ChaCha20Blake3.generate_key` for key generation. Never derive keys from passwords without a proper KDF (Argon2, scrypt, PBKDF2).
180
+
181
+ ### Tag Size
182
+
183
+ The 32-byte (256-bit) tag provides 128-bit security against forgery (birthday bound). This is double the tag size of AES-GCM (128-bit tag, 64-bit forgery security) and XChaCha20-Poly1305 (128-bit tag, 64-bit forgery security).
184
+
185
+ ### What This Is Not
186
+
187
+ - Not a key agreement protocol - use X25519 or Noise for key exchange
188
+ - Not a password hashing function - use Argon2 for password storage
189
+ - Not a digital signature scheme - use Ed25519 for signatures
190
+
191
+ ## Performance
192
+
193
+ This gem peaks at ~1.27 GB/s encrypt on a 2019 MacBook Pro (i7-9750H, Linux
194
+ VM, AVX2). The upstream Rust crate achieves 3+ GB/s on modern hardware (AMD
195
+ EPYC 9R45) without the Ruby FFI overhead.
196
+
197
+ Compared to ChaCha20-Poly1305 and AES-256-GCM via Ruby's OpenSSL bindings
198
+ (same machine):
199
+
200
+ Encrypt:
201
+ ```
202
+ Size CC20-B3 CC20-P1305 AES-GCM
203
+ ---------------------------------------------------
204
+ 64 B 62.0 MB/s 26.2 MB/s 27.8 MB/s
205
+ 256 B 192.5 MB/s 101.0 MB/s 114.2 MB/s
206
+ 1 KB 394.1 MB/s 287.0 MB/s 320.2 MB/s
207
+ 4 KB 639.7 MB/s 713.1 MB/s 797.5 MB/s
208
+ 64 KB 1.17 GB/s 1.30 GB/s 1.92 GB/s
209
+ 256 KB 1.22 GB/s 1.37 GB/s 1.95 GB/s
210
+ 1 MB 1.18 GB/s 1.42 GB/s 2.06 GB/s
211
+ ```
212
+
213
+ Decrypt:
214
+ ```
215
+ Size CC20-B3 CC20-P1305 AES-GCM
216
+ ---------------------------------------------------
217
+ 64 B 62.3 MB/s 28.2 MB/s 30.0 MB/s
218
+ 256 B 191.2 MB/s 108.9 MB/s 119.2 MB/s
219
+ 1 KB 394.3 MB/s 301.3 MB/s 324.7 MB/s
220
+ 4 KB 648.4 MB/s 716.6 MB/s 876.0 MB/s
221
+ 64 KB 1.16 GB/s 1.27 GB/s 1.88 GB/s
222
+ 256 KB 1.19 GB/s 1.35 GB/s 2.00 GB/s
223
+ 1 MB 1.22 GB/s 1.36 GB/s 2.06 GB/s
224
+ ```
225
+
226
+ ChaCha20-BLAKE3 is ~2x faster on small messages (lower per-call overhead).
227
+ AES-256-GCM pulls ahead on large messages on CPUs with AES-NI. On CPUs
228
+ without AES-NI, ChaCha20-BLAKE3 wins across the board. The tradeoff for
229
+ slightly lower bulk throughput is a 256-bit authentication tag (128-bit
230
+ forgery security) vs 128-bit tags in Poly1305 and GCM (64-bit forgery
231
+ security).
232
+
233
+ See [benchmarks/README.md](benchmarks/README.md) for the full 64 B - 64 MiB
234
+ results.
235
+
236
+ ### Ruby binding overhead
237
+
238
+ The Ruby bindings use 2 memory copies per operation (down from 3 in a naive
239
+ implementation) due to unavoidable data copies across the FFI boundary:
240
+
241
+ 1. **Ruby string -> Rust Vec** - Ruby's GC can relocate string buffers at any
242
+ time, so the plaintext must be copied into Rust-owned memory before
243
+ encryption begins. This copy cannot be eliminated safely.
244
+ 2. **Encrypt/decrypt in place** - the bindings call `encrypt_in_place_detached`
245
+ directly on the Rust Vec, avoiding the extra allocation that the upstream
246
+ convenience `encrypt()` function would make internally.
247
+ 3. **Rust Vec -> Ruby string** - after encryption (or successful MAC
248
+ verification on decrypt), the output Ruby string is allocated at the exact
249
+ final size (`str_buf_new`), then filled with a single `cat()` call.
250
+
251
+ The remaining bottleneck at large sizes (>2 MiB) is L3 cache eviction, not
252
+ the FFI overhead.
253
+
254
+ Run the included benchmarks:
255
+
256
+ ```sh
257
+ bundle exec rake bench
258
+ ```
259
+
260
+ ## Building from Source
261
+
262
+ ```sh
263
+ # Install Rust (if needed)
264
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
265
+
266
+ # Install gem dependencies
267
+ bundle install
268
+
269
+ # Compile the native extension
270
+ bundle exec rake compile
271
+
272
+ # Run tests
273
+ bundle exec rake test
274
+
275
+ # Run Rust unit tests
276
+ bundle exec rake cargo_test
277
+
278
+ # Lint
279
+ bundle exec rake clippy
280
+ ```
281
+
282
+ ## License
283
+
284
+ MIT