v / vlib / x / crypto / ascon / digest.v
183 lines · 168 sloc · 5.39 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// Common helpers used by Ascon-Hash256, Ascon-XOF128 and Ascon-CXOF128
6module ascon
7
8import encoding.binary
9
10// The Digest is an internal structure used by Ascon-hashing variants.
11@[noinit]
12struct Digest {
13 State
14mut:
15 // buffer for storing unprocessed data for streaming-way
16 buf []u8 = []u8{len: block_size}
17 // length of leftover unprocessed bytes on the digest buffer
18 length int
19
20 // internal flag
21 done bool
22}
23
24// finish finalizes state by writing last block in the Digest internal buffer.
25// and consequently updates Digest state.
26@[direct_array_access; inline]
27fn (mut d Digest) finish() {
28 if d.length >= d.buf.len {
29 panic('Digest.finish: internal error')
30 }
31 // Process for the last block stored on the internal buffer
32 d.State.e0 ^= pad(d.length)
33 d.State.e0 ^= load_bytes(d.buf[..d.length], d.length)
34
35 // Permutation step was done in squeezing-phase
36 // ascon_pnr(mut d.State, .ascon_prnd_12)
37
38 // zeroing Digest buffer
39 d.length = 0
40 unsafe { d.buf.reset() }
41
42 // After finishing phase, we can't write data anymore into state
43 d.done = true
44}
45
46// absorb absorbs message msg_ into Digest state.
47@[direct_array_access]
48fn (mut d Digest) absorb(msg_ []u8) int {
49 // nothing to absorb, just return
50 if msg_.len == 0 {
51 return 0
52 }
53 // Absorbing messages into Digest state working in streaming-way.
54 // Its continuesly updates internal state until you call `.finish` or `.free` it.
55 // Firstly, it would checking unprocessed bytes on internal buffer, and append it
56 // with bytes from messasge to align with block_size.
57 // And then absorb this buffered message into state.
58 // The process continues until the last partial block thats maybe below the block_size.
59 // If its happens, it will be stored on the Digest internal buffer for later processing.
60 mut msg := msg_.clone()
61 unsafe {
62 // Check if internal buffer has previous unprocessed bytes.
63 // If its on there, try to empty the buffer.
64 if d.length > 0 {
65 // There are bytes in the d.buf, append it with bytes taken from msg
66 if d.length + msg.len >= block_size {
67 n := copy(mut d.buf[d.length..], msg)
68 msg = msg[n..]
69 d.length += n
70 // If this d.buf length has reached block_size bytes, absorb it.
71 if d.length == block_size {
72 d.State.e0 ^= binary.little_endian_u64(d.buf)
73 ascon_pnr(mut d.State, .ascon_prnd_12)
74 // reset the internal buffer
75 d.length = 0
76 d.buf.reset()
77 }
78 } else {
79 // Otherwise, still fit to buffer, but nof fully fills the d.buf
80 // just stores into buffer without processing
81 n := copy(mut d.buf[d.length..], msg)
82 msg = msg[n..]
83 d.length += n
84 }
85 }
86 // process for full block
87 for msg.len >= block_size {
88 d.State.e0 ^= binary.little_endian_u64(msg[0..block_size])
89 msg = msg[block_size..]
90 ascon_pnr(mut d.State, .ascon_prnd_12)
91 }
92 // If there are partial block, just stored into buffer.
93 if msg.len > 0 {
94 n := copy(mut d.buf[d.length..], msg)
95 msg = msg[n..]
96 d.length += n
97 }
98 return msg_.len - msg.len
99 }
100}
101
102// squeeze squeezes the state and calculates checksum output for the current state.
103// It accepts destination buffer with desired buffer length you want to output.
104@[direct_array_access; inline]
105fn (mut d Digest) squeeze(mut dst []u8) int {
106 // nothing to store, just return unchanged
107 if dst.len == 0 {
108 return 0
109 }
110 // check
111 if dst.len > max_hash_size {
112 panic('Digest.squeeze: invalid dst.len')
113 }
114 // The squeezing phase begins after msg is absorbed with an
115 // permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state:
116 ascon_pnr(mut d.State, .ascon_prnd_12)
117
118 mut pos := 0
119 mut clen := dst.len
120 // process for full block size
121 for clen >= block_size {
122 binary.little_endian_put_u64(mut dst[pos..pos + 8], d.State.e0)
123 ascon_pnr(mut d.State, .ascon_prnd_12)
124 pos += block_size
125 clen -= block_size
126 }
127 // final output, the resulting hash is the concatenation of hash blocks
128 store_bytes(mut dst[pos..], d.State.e0, clen)
129 pos += clen
130
131 // for make sure, assert it here
132 assert pos == dst.len
133
134 return pos
135}
136
137@[direct_array_access; inline]
138fn ascon_generic_hash(mut s State, msg []u8, size int) []u8 {
139 // Assumed state was correctly initialized
140 // Absorbing the message
141 mut pos := 0
142 // Check if msg has non-null length, if yes, absorb it.
143 // Otherwise, just pad it
144 if _likely_(msg.len > 0) {
145 mut msg_len := msg.len
146 for msg_len >= block_size {
147 block := unsafe { msg[pos..pos + block_size] }
148 s.e0 ^= binary.little_endian_u64(block)
149 pos += block_size
150 msg_len -= block_size
151 ascon_pnr(mut s, .ascon_prnd_12)
152 }
153 // Absorb the last partial message block
154 last_block := unsafe { msg[pos..] }
155 s.e0 ^= u64(0x01) << (8 * last_block.len) // pad(last_block.len)
156 if last_block.len > 0 {
157 s.e0 ^= load_bytes(last_block, last_block.len)
158 }
159 } else {
160 // Otherwise, just pad it
161 s.e0 ^= u64(0x01)
162 }
163 // reset pos
164 pos = 0
165
166 // Squeezing phase
167 //
168 // The squeezing phase begins after msg is absorbed with an
169 // permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state:
170 ascon_pnr(mut s, .ascon_prnd_12)
171 mut out := []u8{len: size}
172 mut clen := out.len
173 for clen >= block_size {
174 binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
175 ascon_pnr(mut s, .ascon_prnd_12)
176 pos += block_size
177 clen -= block_size
178 }
179 // final output, the resulting 256-bit digest is the concatenation of hash blocks
180 store_bytes(mut out[pos..], s.e0, clen)
181
182 return out
183}
184