v2 / vlib / x / crypto / ascon / xof.v
322 lines · 287 sloc · 9.26 KB · 50d03eb7b93bcdcfa90a43fc6cfa68916f38d28e
Raw
1// Copyright ©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 implements Ascon-XOF128, an Ascon-based Extendable-output Function (XOF)
6// and their variant, Ascon-CXOF128 from NIST.SP.800-232 standard.
7// Ascon-XOF128 is similar to Ascon-Hash256 where Ascon-XOF128 accepts an additional input
8// which specifies the desired output length in bits.
9module ascon
10
11import encoding.binary
12
13// max_hash_size is a maximum size of Ascon-XOF128 (and Ascon-CXOF128) checksum output.
14// Its very rare where checksum output bigger than 512-bits, So, we limiting it to prevent unconditional thing.
15// This limitation was only occurs on this module, wee can change it later.
16pub const max_hash_size = 4096 // in bytes
17
18// default_xof_size is the size of Ascon-XOF128 (and Ascon-CXOF128) checksum that has 512-bits length.
19pub const default_xof_size = 64
20
21// xof128_initial_state is a precomputed value for Ascon-XOF128 state.
22// See the comment on `hash256_initial_state` about the values
23const xof128_initial_state = State{
24 e0: 0xda82ce768d9447eb
25 e1: 0xcc7ce6c75f1ef969
26 e2: 0xe7508fd780085631
27 e3: 0x0ee0ea53416b58cc
28 e4: 0xe0547524db6f0bde
29}
30
31// xof128 creates an Ascon-XOF128 checksum of msg with specified desired size of output.
32pub fn xof128(msg []u8, size int) ![]u8 {
33 if size > max_hash_size {
34 return error('xof128: invalid size')
35 }
36 mut s := xof128_initial_state
37 return ascon_generic_hash(mut s, msg, size)
38}
39
40// xof128_64 creates a 64-bytes of Ascon-XOF128 checksum of msg.
41pub fn xof128_64(msg []u8) ![]u8 {
42 return xof128(msg, default_xof_size)!
43}
44
45// Xof128 is an opaque provides an implementation of Ascon-XOF128 from NIST.SP.800-232 standard.
46// Its implements `hash.Hash` interface with checksum size stored on instance creation.
47@[noinit]
48pub struct Xof128 {
49 Digest
50mut:
51 // The size of Xof128 cheksum, when you dont specify it
52 size int = default_xof_size
53}
54
55// new_xof128 creates a new Ascon-XOF128 instance with provided size parameter.
56pub fn new_xof128(size int) &Xof128 {
57 if size < 1 || size > max_hash_size {
58 panic('new_xof128: invalid size')
59 }
60 return &Xof128{
61 Digest: Digest{
62 State: xof128_initial_state
63 }
64 size: size
65 }
66}
67
68// size returns an underlying size of Xof128 checksum in fixed-sized manner.
69// Internally, its return underlying size stored on current Xof128 instance.
70pub fn (x &Xof128) size() int {
71 return x.size
72}
73
74// block_size returns an underlying Xof128 block size operates on, ie, 8-bytes
75pub fn (x &Xof128) block_size() int {
76 return block_size
77}
78
79// clone returns a clone of x on the current state.
80fn (x &Xof128) clone() &Xof128 {
81 return &Xof128{
82 Digest: x.Digest
83 size: x.size
84 }
85}
86
87// write writes out the content of message and updates internal Xof128 state.
88pub fn (mut x Xof128) write(msg []u8) !int {
89 if x.Digest.done {
90 panic('Digest: writing after done ')
91 }
92 return x.Digest.absorb(msg)
93}
94
95// sum returns an Ascon-XOF128 checksum of the bytes in msg.
96// Its produces `x.size` of checksum bytes. If you want to more
97// extendible output, use `.read` call instead.
98pub fn (mut x Xof128) sum(msg []u8) []u8 {
99 // working on the clone of the h, so we can keep writing
100 mut x0 := x.clone()
101 _ := x0.write(msg) or { panic(err) }
102 x0.Digest.finish()
103 mut dst := []u8{len: x.size}
104 x0.Digest.squeeze(mut dst)
105 x0.reset()
106 return dst
107}
108
109// read tries to read `dst.len` bytes of hash output from current Xof128 state and stored into dst.
110// Note: 1 ≤ dst.len ≤ max_hash_size.
111pub fn (mut x Xof128) read(mut dst []u8) !int {
112 // Left unchanged
113 if dst.len == 0 {
114 return 0
115 }
116 // Check output size
117 if dst.len > max_hash_size {
118 panic('Xof128.read: invalid size')
119 }
120 mut x0 := x.clone()
121 x0.Digest.finish()
122 n := x0.Digest.squeeze(mut dst)
123 x0.reset()
124 return n
125}
126
127// reset resets internal Xof128 state into default initialized state.
128pub fn (mut x Xof128) reset() {
129 // we dont reset the x.size
130 unsafe { x.Digest.buf.reset() }
131 x.Digest.length = 0
132 x.Digest.done = false
133 x.Digest.State = xof128_initial_state
134}
135
136// free releases out the resources taken by the `x`. Dont use x after .free call.
137@[unsafe]
138pub fn (mut x Xof128) free() {
139 $if prealloc {
140 return
141 }
142 unsafe {
143 x.Digest.buf.free()
144 }
145 // Mark it as unusable
146 x.Digest.done = true
147}
148
149// Ascon-CXOF128
150//
151// Ascon-CXOF128 is customized variant of Ascon-XOF128 that extends its
152// functionality by incorporating a user-defined customization string.
153// In addition to the message 𝑀 and output length 𝐿, Ascon-CXOF128 takes
154// the customization string 𝑍 as input.
155
156// The length of the customization string shall be at most 2048 bits (i.e., 256 bytes)
157const max_cxof128_cstring = 256
158
159// cxof128_initial_state is a precomputed value for Ascon-CXOF128 state.
160// See the comment on `hash256_initial_state` about the values
161const cxof128_initial_state = State{
162 e0: 0x675527c2a0e8de03
163 e1: 0x43d12d7dc0377bbc
164 e2: 0xe9901dec426e81b5
165 e3: 0x2ab14907720780b6
166 e4: 0x8f3f1d02d432bc46
167}
168
169// cxof128 creates an Ascon-CXOF128 checksum of msg with supplied size and custom string cs.
170pub fn cxof128(msg []u8, size int, cs []u8) ![]u8 {
171 // Initialize CXof128 state with precomputed-value and absorb the customization string
172 mut s := cxof128_initial_state
173 cxof128_absorb_custom_string(mut s, cs)
174 return ascon_generic_hash(mut s, msg, size)
175}
176
177// cxof128_64 creates a 64-bytes of Ascon-CXOF128 checksum of msg with supplied custom string in cs.
178pub fn cxof128_64(msg []u8, cs []u8) ![]u8 {
179 return cxof128(msg, default_xof_size, cs)!
180}
181
182// CXof128 is an opaque provides an implementation of Ascon-CXOF128 from NIST.SP.800-232 standard.
183// Its implements `hash.Hash` interface with checksum-size stored on instance creation.
184@[noinit]
185pub struct CXof128 {
186 Digest
187mut:
188 // Customization string
189 cs []u8
190 // checksum size, for fixed-output
191 size int = default_xof_size
192}
193
194// new_cxof128 creates a new Ascon-CXOF128 instanace with cheksum size
195// was set to size and custom string in cs. It returns error on fails.
196pub fn new_cxof128(size int, cs []u8) !&CXof128 {
197 if cs.len > max_cxof128_cstring {
198 return error('CXof128: custom string length exceed limit')
199 }
200 if size < 1 || size > max_hash_size {
201 return error('CXof128: invalid size')
202 }
203 // Initialize CXof128 state with precomputed-value and absorb the customization string
204 mut s := cxof128_initial_state
205 cxof128_absorb_custom_string(mut s, cs)
206
207 return &CXof128{
208 Digest: Digest{
209 State: s
210 }
211 cs: cs
212 size: size
213 }
214}
215
216// size returns an underlying size of CXof128 checksum in fixed-sized manner.
217// Internally, its return underlying size stored on current CXof128 instance.
218pub fn (x &CXof128) size() int {
219 return x.size
220}
221
222// block_size returns an underlying CXof128 block size operates on, ie, 8-bytes
223pub fn (x &CXof128) block_size() int {
224 return block_size
225}
226
227// write writes out the content of message and updates internal CXof128 state.
228pub fn (mut x CXof128) write(msg []u8) !int {
229 if x.Digest.done {
230 panic('CXof128: writing after done ')
231 }
232 return x.Digest.absorb(msg)
233}
234
235// sum returns an Ascon-CXOF128 checksum of the bytes in msg.
236// Its produces `x.size` of checksum bytes. If you want to more
237// extendible output, use `.read` call instead.
238pub fn (mut x CXof128) sum(msg []u8) []u8 {
239 // working on the clone of the h, so we can keep writing
240 mut x0 := x.clone()
241 _ := x0.write(msg) or { panic(err) }
242 x0.Digest.finish()
243 mut dst := []u8{len: x.size}
244 x0.Digest.squeeze(mut dst)
245 x0.reset()
246 return dst
247}
248
249// read tries to read `dst.len` bytes of hash output from current CXof128 state and stored into dst.
250pub fn (mut x CXof128) read(mut dst []u8) !int {
251 // Left unchanged, nothing space to store checksum
252 if dst.len == 0 {
253 return 0
254 }
255 if dst.len > max_hash_size {
256 panic('CXof128.read: invalid size')
257 }
258 mut x0 := x.clone()
259 x0.Digest.finish()
260 n := x0.Digest.squeeze(mut dst)
261 x0.reset()
262 return n
263}
264
265// clone returns a clone of x on the current state.
266fn (x &CXof128) clone() &CXof128 {
267 return &CXof128{
268 Digest: x.Digest
269 size: x.size
270 cs: x.cs
271 }
272}
273
274// reset resets internal CXof128 state into default initialized state.
275pub fn (mut x CXof128) reset() {
276 // we dont reset the x.size and custom string
277 unsafe { x.Digest.buf.reset() }
278 x.Digest.length = 0
279 x.Digest.done = false
280 x.Digest.State = cxof128_initial_state
281 // reabsorbs custom string
282 cxof128_absorb_custom_string(mut x.Digest.State, x.cs)
283}
284
285// free releases out the resources taken by the `x`. Dont use x after .free call.
286@[unsafe]
287pub fn (mut x CXof128) free() {
288 $if prealloc {
289 return
290 }
291 unsafe {
292 x.Digest.buf.free()
293 }
294 // Mark it as unusable
295 x.Digest.done = true
296}
297
298// cxof128_absorb_custom_string performs absorbing phase of custom string in cs for Ascon-CXOF128.
299@[direct_array_access; inline]
300fn cxof128_absorb_custom_string(mut s State, cs []u8) {
301 // absorb Z0, the length of the customization string (in bits) encoded as a u64
302 s.e0 ^= u64(cs.len) << 3
303 ascon_pnr(mut s, .ascon_prnd_12)
304
305 // absorb the customization string
306 mut zlen := cs.len
307 mut zidx := 0
308 for zlen >= block_size {
309 block := unsafe { cs[zidx..zidx + block_size] }
310 s.e0 ^= binary.little_endian_u64(block)
311 ascon_pnr(mut s, .ascon_prnd_12)
312
313 // updates a index
314 zlen -= block_size
315 zidx += block_size
316 }
317 // absorb final customization string
318 last_block := unsafe { cs[zidx..] }
319 s.e0 ^= load_bytes(last_block, last_block.len)
320 s.e0 ^= pad(last_block.len)
321 ascon_pnr(mut s, .ascon_prnd_12)
322}
323