Skip to content

Commit 60b0ee9

Browse files
authored
x.crypto.chacha20poly1305: add a nonce-misuse resistant and key-committing feature (#25459)
1 parent af7cc28 commit 60b0ee9

4 files changed

Lines changed: 887 additions & 2 deletions

File tree

‎vlib/x/crypto/chacha20poly1305/README.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ module and `x.crypto.poly1305` message authentication code (MAC) module.
1010
> This is an absolutely experimental module, which is subject to change.
1111
> Please use it carefully, thoroughly and wisely.
1212
13+
## Supported features
14+
The implemented features at the time of writing (2025/10/08) are:
15+
- The standard ChaCha20 and Poly1305 construct for IETF Protocols defined in [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439)
16+
- ChaCha20 Poly1305 AEAD construct with 8-bytes nonce.
17+
- Support for eXtended ChaCha20 Poly1305 AEAD construct with 24 bytes nonce.
18+
- Support for nonce-misuse resistent and key-commiting AEAD through ChaCha20-Poly1305-PSIV construction defined in the [A Robust Variant of ChaCha20-Poly1305](https://eprint.iacr.org/2025/222) paper.
19+
1320
## Examples
1421

1522
```v
@@ -18,8 +25,8 @@ import x.crypto.chacha20poly1305
1825
1926
fn main() {
2027
// plaintext message to be encrypted and authenticated
21-
message := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."
22-
.bytes()
28+
message := "Ladies and Gentlemen of the class of '99: If I could offer you only one
29+
tip for the future, sunscreen would be it.".bytes()
2330
2431
// sets your secure random key
2532
key := hex.decode('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f')!
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright (c) 2025 blackshirt.
2+
// Use of this source code is governed by an MIT license
3+
// that can be found in the LICENSE file.
4+
//
5+
// This file contains benchmarking code for standard AEAD_CHACHA20_POLY1305 encryption
6+
// and decryption compared to AEAD_CHACHA20_POLY1305 with PSIV construct.
7+
//
8+
// This output on my test.
9+
// Standard ChaCha20Poly1305 AEAD and PSIV construct output performance comparison
10+
// ===============================================================================
11+
// Iterations per test: 1000
12+
// ------------------------------------------------------------------------------------------------------
13+
// Data Size | Std | PSIV | Enc (std/psiv) || Std | PSIV | Dec (std/psiv)|
14+
// ------------------------------------------------------------------------------------------------------
15+
// 6 B | 16.00ms | 19.00ms | 0.85x || 16.00ms | 19.00ms | 0.81x |
16+
// 8 B | 15.00ms | 19.00ms | 0.81x || 16.00ms | 18.00ms | 0.87x |
17+
// 12 B | 15.00ms | 20.00ms | 0.76x || 16.00ms | 20.00ms | 0.77x |
18+
// 16 B | 15.00ms | 21.00ms | 0.72x || 15.00ms | 19.00ms | 0.80x |
19+
// 64 B | 21.00ms | 25.00ms | 0.82x || 21.00ms | 26.00ms | 0.80x |
20+
// 75 B | 28.00ms | 35.00ms | 0.81x || 34.00ms | 44.00ms | 0.77x |
21+
// 256 B | 55.00ms | 59.00ms | 0.94x || 50.00ms | 63.00ms | 0.80x |
22+
// 1028 B | 174.00ms | 242.00ms | 0.72x || 178.00ms | 227.00ms | 0.78x |
23+
// 2049 B | 361.00ms | 522.00ms | 0.69x || 399.00ms | 558.00ms | 0.71x |
24+
// ------------------------------------------------------------------------------------------------------
25+
// Total | 703.00ms | 965.00ms | 0.73x || 748.00ms | 1.00s | 0.75x|
26+
// ------------------------------------------------------------------------------------------------------
27+
//
28+
// Per-operation averages:
29+
// Standard ChaCha20Poly1305 encrypt: 78209 ns per hash
30+
// ChaCha20Poly1305 PSIV encrypt: 107325 ns per hash
31+
//
32+
// Standard ChaCha20Poly1305 decrypt: 83151 ns per hash
33+
// ChaCha20Poly1305 PSIV decrypt: 111155 ns per hash
34+
module main
35+
36+
import rand
37+
import time
38+
import x.crypto.chacha20poly1305
39+
40+
const benchmark_iterations = 1000
41+
42+
const test_data_sizes = [6, 8, 12, 16, 64, 75, 256, 1028, 2049]
43+
44+
// standard AEAD_CHACHA20_POLY1305 encryption
45+
fn benchmark_aead_std_encrypt(msg []u8, key []u8, nonce []u8, ad []u8, iterations int) time.Duration {
46+
start := time.now()
47+
for _ in 0 .. iterations {
48+
_ := chacha20poly1305.encrypt(msg, key, nonce, ad) or { panic(err) }
49+
}
50+
return time.since(start)
51+
}
52+
53+
// standard AEAD_CHACHA20_POLY1305 decryption
54+
fn benchmark_aead_std_decrypt(data []u8, key []u8, nonce []u8, ad []u8, iterations int) time.Duration {
55+
start := time.now()
56+
for _ in 0 .. iterations {
57+
_ := chacha20poly1305.decrypt(data, key, nonce, ad) or { panic(err) }
58+
}
59+
return time.since(start)
60+
}
61+
62+
// psiv AEAD_CHACHA20_POLY1305 encryption
63+
fn benchmark_aead_psiv_encrypt(msg []u8, key []u8, nonce []u8, ad []u8, iterations int) time.Duration {
64+
start := time.now()
65+
for _ in 0 .. iterations {
66+
_ := chacha20poly1305.psiv_encrypt(msg, key, nonce, ad) or { panic(err) }
67+
}
68+
return time.since(start)
69+
}
70+
71+
// psiv AEAD_CHACHA20_POLY1305 decryption
72+
fn benchmark_aead_psiv_decrypt(data []u8, key []u8, nonce []u8, ad []u8, iterations int) time.Duration {
73+
start := time.now()
74+
for _ in 0 .. iterations {
75+
_ := chacha20poly1305.psiv_decrypt(data, key, nonce, ad) or { panic(err) }
76+
}
77+
return time.since(start)
78+
}
79+
80+
fn format_duration(d time.Duration) string {
81+
if d.microseconds() < 1000 {
82+
return '${d.microseconds():6}μs'
83+
} else if d.milliseconds() < 1000 {
84+
return '${f64(d.milliseconds()):6.2f}ms'
85+
} else {
86+
return '${f64(d.seconds()):6.2f}s'
87+
}
88+
}
89+
90+
const data_title = 'Data Size'
91+
const aead_std_enc = 'Std'
92+
const aead_psiv_enc = 'PSIV'
93+
const aead_std_dec = 'Std'
94+
const aead_psiv_dec = 'PSIV'
95+
const ratio_std_psiv_enc_title = 'Enc (std/psiv)'
96+
const ratio_std_psiv_dec_title = 'Dec (std/psiv)'
97+
98+
fn main() {
99+
println('Standard ChaCha20Poly1305 AEAD and PSIV construct output performance comparison')
100+
println('===============================================================================')
101+
println('Iterations per test: ${benchmark_iterations}')
102+
103+
println('${'-'.repeat(102)}')
104+
println('${data_title:12} | ${aead_std_enc:10} | ${aead_psiv_enc:10} | ${ratio_std_psiv_enc_title:12} || ${aead_std_dec:10} | ${aead_psiv_dec:12} | ${ratio_std_psiv_dec_title:12}|')
105+
println('${'-'.repeat(102)}')
106+
107+
mut total_std_encrypt := time.Duration(0)
108+
mut total_std_decrypt := time.Duration(0)
109+
mut total_psiv_encrypt := time.Duration(0)
110+
mut total_psiv_decrypt := time.Duration(0)
111+
112+
key := rand.bytes(32)!
113+
nonce := rand.bytes(12)!
114+
for size in test_data_sizes {
115+
ad := rand.bytes(size)!
116+
test_msg := rand.bytes(size)!
117+
118+
// Warm up
119+
out0 := chacha20poly1305.encrypt(test_msg, key, nonce, ad)!
120+
_ := chacha20poly1305.decrypt(out0, key, nonce, ad)!
121+
122+
out1 := chacha20poly1305.psiv_encrypt(test_msg, key, nonce, ad)!
123+
_ := chacha20poly1305.psiv_decrypt(out1, key, nonce, ad)!
124+
125+
// Benchmark Standard AEAD_CHACHA20_POLY1305 encryption
126+
std_encrypt_time := benchmark_aead_std_encrypt(test_msg, key, nonce, ad, benchmark_iterations)
127+
128+
// Benchmark Standard AEAD_CHACHA20_POLY1305 decryption
129+
std_decrypt_time := benchmark_aead_std_decrypt(out0, key, nonce, ad, benchmark_iterations)
130+
131+
// Benchmark AEAD_CHACHA20_POLY1305 PSIV encryption
132+
psiv_encrypt_time := benchmark_aead_psiv_encrypt(test_msg, key, nonce, ad, benchmark_iterations)
133+
134+
// Benchmark AEAD_CHACHA20_POLY1305 PSIV decryption
135+
psiv_decrypt_time := benchmark_aead_psiv_decrypt(out1, key, nonce, ad, benchmark_iterations)
136+
137+
// Calculate ratio Standard/PSIV encryption
138+
ratio_std_psiv_encrypt := f64(std_encrypt_time.nanoseconds()) / f64(psiv_encrypt_time.nanoseconds())
139+
140+
// Calculate ratio Standard/PSIV decryption
141+
ratio_std_psiv_decrypt := f64(std_decrypt_time.nanoseconds()) / f64(psiv_decrypt_time.nanoseconds())
142+
143+
stdencrypt_str := format_duration(std_encrypt_time)
144+
stddecrypt_str := format_duration(std_decrypt_time)
145+
psivencrypt_str := format_duration(psiv_encrypt_time)
146+
psivdecrypt_str := format_duration(psiv_decrypt_time)
147+
148+
ratio_std_psiv_encrypt_str := '${ratio_std_psiv_encrypt:6.2f}x'
149+
ratio_std_psiv_decrypt_str := '${ratio_std_psiv_decrypt:6.2f}x'
150+
151+
println('${size:10} B | ${stdencrypt_str:10} | ${psivencrypt_str:10} | ${ratio_std_psiv_encrypt_str:14} || ${stddecrypt_str:10} | ${psivdecrypt_str:11} | ${ratio_std_psiv_decrypt_str:12} |')
152+
153+
total_std_encrypt += std_encrypt_time
154+
total_std_decrypt += std_decrypt_time
155+
total_psiv_encrypt += psiv_encrypt_time
156+
total_psiv_decrypt += psiv_decrypt_time
157+
}
158+
159+
println('${'-'.repeat(102)}')
160+
161+
// Overall performance comparison
162+
overall_std_psiv_encrypt_ratio := f64(total_std_encrypt.nanoseconds()) / f64(total_psiv_encrypt.nanoseconds())
163+
overall_std_psiv_decrypt_ratio := f64(total_std_decrypt.nanoseconds()) / f64(total_psiv_decrypt.nanoseconds())
164+
165+
total_title := 'Total'
166+
println('${total_title:12} | ${format_duration(total_std_encrypt):10} | ${format_duration(total_psiv_encrypt):10} | ${overall_std_psiv_encrypt_ratio:13.2f}x || ${format_duration(total_std_decrypt):10} | ${format_duration(total_psiv_decrypt):12} | ${overall_std_psiv_decrypt_ratio:12.2f}x|')
167+
println('${'-'.repeat(102)}')
168+
169+
println('')
170+
println('Per-operation averages:')
171+
avg_std_encrypt := total_std_encrypt.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
172+
avg_std_decrypt := total_std_decrypt.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
173+
avg_psiv_encrypt := total_psiv_encrypt.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
174+
avg_psiv_decrypt := total_psiv_decrypt.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
175+
176+
println(' Standard ChaCha20Poly1305 encrypt:\t ${avg_std_encrypt:8} ns per hash')
177+
println(' ChaCha20Poly1305 PSIV encrypt:\t ${avg_psiv_encrypt:8} ns per hash')
178+
println('')
179+
println(' Standard ChaCha20Poly1305 decrypt:\t ${avg_std_decrypt:8} ns per hash')
180+
println(' ChaCha20Poly1305 PSIV decrypt:\t ${avg_psiv_decrypt:8} ns per hash')
181+
println('')
182+
}

0 commit comments

Comments
 (0)