99// See the detail on the [A Robust Variant of ChaCha20-Poly1305](https://eprint.iacr.org/2025/222).
1010module chacha20poly1305
1111
12- import arrays
1312import encoding.binary
1413import crypto.internal.subtle
1514import x.crypto.chacha20
@@ -157,42 +156,51 @@ pub fn (c Chacha20Poly1305RE) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u
157156// for encrypting (or decrypting) message.
158157@[direct_array_access]
159158fn psiv_encrypt_internal (plaintext []u8 , key []u8 , tag []u8 , nonce []u8 ) ! []u8 {
160- tctr , trest := split_tag ( tag)
161- mut ctr := binary.little_endian_u64 (tctr )
159+ // loads the counter from the first 8-bytes of the tag input
160+ mut ctr := binary.little_endian_u64 (tag[ 0 .. 8 ] )
162161
163- mut dst := []u8 {cap: plaintext.len}
162+ // setup some temporary vars
163+ mut dst := []u8 {len: plaintext.len}
164164 mut tc := []u8 {len: 8 } // counter buffer
165165 mut s := chacha20 .State{}
166166 mut b64 := []u8 {len: 64 } // state buffer
167+ mut tt := merge_drv_key (key, nonce, tag[0 ..8 ], tag[8 ..16 ])
167168
168- // split out plaintext messages into 64-bytes chunk, and process them
169- // chunk by chunk.
170- chunks := arrays.chunk[u8 ](plaintext, 64 )
171- for chunk in chunks {
169+ mut j := 0
170+ mut n := 0
171+
172+ // process for every bytes on plaintext input
173+ for plaintext[n..].len > 0 {
174+ // how many block of bytes available to process on
175+ want_len := if plaintext[n..].len < 64 { plaintext[n..].len } else { 64 }
172176 // loads current counter
173177 binary.little_endian_put_u64 (mut tc, ctr)
174178
175- // loads 64-bytes of merged key into state s and then perform chacha20 qround.
176- // then xor-ing every bytes of result with the bytes in chunk and appended into
177- // destination output buffer.
178- unpack_into_state (mut s, merge_drv_key (key, nonce, tc, trest))
179- buf := chacha20_core (s)
180- pack64_from_state (mut b64 , buf)
181- for i, v in chunk {
182- o := v ^ b64 [i]
183- dst << o
179+ // updates derived keys with current counter, scrambled with chacha20_core and
180+ // puts state into b64 buffer
181+ unsafe { vmemcpy (tt[48 ], tc.data, tc.len) }
182+ unpack_into_state (mut s, tt)
183+ ws := chacha20_core (s)
184+ pack64_from_state (mut b64 , ws)
185+
186+ // xor every bytes of plaintext with bytes on b64, stores result in dst
187+ for i in 0 .. want_len {
188+ dst[j] = plaintext[j] ^ b64 [i]
189+ j++
184190 }
185191 // updates current counter and returns error on overflow.
186192 ctr + = 1
187193 if ctr == 0 {
188194 return error ('counter overflowing' )
189195 }
196+ n + = want_len
190197 }
191- // reset (release) temporary allocated resources
198+ // reset (release) temporary allocated resources and return the result.
192199 unsafe {
193200 tc.free ()
194201 s.reset ()
195202 b64 .free ()
203+ tt.free ()
196204 }
197205 return dst
198206}
@@ -216,18 +224,17 @@ fn psiv_gen_tag(mut po poly1305.Poly1305, input []u8, ad_len int, mac_key []u8,
216224 unpack_into_state (mut x, drv_key)
217225 ws := chacha20_core (x)
218226
219- // truncating state output into tag sized bytes
220- mut tag := [] u8 {len: tag_size}
221- pack16_from_state (mut tag , ws)
227+ // truncating state output into tag_sized bytes. As a note, we reuse digest buffer allocated
228+ // on previous step to store the result.
229+ pack16_from_state (mut digest , ws)
222230
223- // releases (reset) temporary allocated resources
231+ // releases (reset) temporary allocated resources and return the result.
224232 unsafe {
225233 drv_key.free ()
226- digest.free ()
227234 ws.reset ()
228235 x.reset ()
229236 }
230- return tag
237+ return digest
231238}
232239
233240// psiv_init initializes and expands master key into desired psiv needed construct.
0 commit comments