1+ # Constantine
2+ # Copyright (c) 2018-2019 Status Research & Development GmbH
3+ # Copyright (c) 2020-Present Mamy André-Ratsimbazafy
4+ # Licensed and distributed under either of
5+ # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
6+ # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
7+ # at your option. This file may not be copied, modified, or distributed except according to those terms.
8+
9+ import ../ zoo_exports
10+
11+ import
12+ ../ platforms/ [abstractions, views],
13+ ./ keccak/ keccak_generic
14+
15+ # Keccak, the hash function underlying SHA3
16+ # --------------------------------------------------------------------------------
17+ #
18+ # References:
19+ # - https://keccak.team/keccak_specs_summary.html
20+ # - https://keccak.team/files/Keccak-reference-3.0.pdf
21+ # - https://keccak.team/files/Keccak-implementation-3.2.pdf
22+ # - SHA3 (different padding): https://csrc.nist.gov/publications/detail/fips/202/final
23+ # - https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
24+
25+ # Sponge API
26+ # --------------------------------------------------------------------------------
27+ #
28+ # References:
29+ # - https://keccak.team/keccak_specs_summary.html
30+ # - https://keccak.team/files/SpongeFunctions.pdf
31+ # - https://keccak.team/files/CSF-0.1.pdf
32+ #
33+ # Keccak[r,c](Mbytes || Mbits) {
34+ # # Padding
35+ # d = 2^|Mbits| + sum for i=0..|Mbits|-1 of 2^i*Mbits[i]
36+ # P = Mbytes || d || 0x00 || … || 0x00
37+ # P = P xor (0x00 || … || 0x00 || 0x80)
38+ #
39+ # # Initialization
40+ # S[x,y] = 0, for (x,y) in (0…4,0…4)
41+ #
42+ # # Absorbing phase
43+ # for each block Pi in P
44+ # S[x,y] = S[x,y] xor Pi[x+5*y], for (x,y) such that x+5*y < r/w
45+ # S = Keccak-f[r+c](S)
46+ #
47+ # # Squeezing phase
48+ # Z = empty string
49+ # while output is requested
50+ # Z = Z || S[x,y], for (x,y) such that x+5*y < r/w
51+ # S = Keccak-f[r+c](S)
52+ #
53+ # return Z
54+ # }
55+
56+ # Duplex construction
57+ # --------------------------------------------------------
58+ # - https://keccak.team/sponge_duplex.html
59+ # - https://keccak.team/files/SpongeDuplex.pdf
60+ # - https://eprint.iacr.org/2011/499.pdf: Duplexing the Sponge
61+ # - https://eprint.iacr.org/2023/522.pdf: SAFE - Sponge API for Field Element
62+ # - https://hackmd.io/@7dpNYqjKQGeYC7wMlPxHtQ/ByIbpfX9c
63+ #
64+ # The original duplex construction described by the Keccak team
65+ # is "absorb-permute-squeeze"
66+ # Paper https://eprint.iacr.org/2022/1340.pdf
67+ # goes over other approaches.
68+ #
69+ # We follow the original intent:
70+ # - permute required when transitioning between absorb->squeeze
71+ # - no permute required when transitioning between squeeze->absorb
72+ # This may change depending on protocol requirement.
73+ # This is inline with the SAFE (Sponge API for FIeld Element) approach
74+
75+ # Types and constants
76+ # ----------------------------------------------------------------
77+
78+ type
79+ KeccakContext * [bits: static int , delimiter: static byte ] = object
80+
81+ # Context description
82+ # - `state` is the permutation state, it is update only
83+ # prior to a permutation
84+ # - `buf` is a message buffer to store partial state updates
85+ # - `absorb_offset` tracks how filled the message buffer is
86+ # - `squeeze_offset` tracks the write position in the output buffer
87+ #
88+ # Subtilities:
89+ # Duplex construction requires a state permutation when
90+ # transitioning between absorb and squeezing phase.
91+ # After an absorb, squeeze_offset is incremented by the sponge `rate`
92+ # This signals the need of a permutation before squeeze.
93+ # Similarly after a squeeze, absorb_offset is incremented by the sponge rate.
94+ # The real offset can be recovered with a substraction
95+ # to properly update the state.
96+
97+ H {.align : 64 .}: KeccakState
98+ buf {.align : 64 .}: array [bits div 8 , byte ]
99+ absorb_offset: int32
100+ squeeze_offset: int32
101+
102+ keccak256* = KeccakContext [256 , 0x 01 ]
103+ sha3_256* = KeccakContext [256 , 0x 06 ]
104+
105+ template rate (ctx: KeccakContext ): int =
106+ 200 - 2 * (ctx.bits div 8 )
107+
108+ # Internals
109+ # ----------------------------------------------------------------
110+
111+ # No exceptions allowed in core cryptographic operations
112+ {.push raises : [].}
113+ {.push checks : off .}
114+
115+ func absorbBuffer (ctx: var KeccakContext ) {.inline .} =
116+ ctx.H.hashMessageBlocks_generic (ctx.buf.asUnchecked (), numBlocks = 1 )
117+ ctx.buf.setZero ()
118+ # Note: in certain case like authenticated encryption
119+ # we might want to absorb at the same position that have been squeezed
120+ # hence we don't reset the absorb_offset to 0
121+ # The buf is zeroed which is the neutral element for xor.
122+
123+ # Public API
124+ # ----------------------------------------------------------------
125+
126+ template digestSize * (H: type KeccakContext ): int =
127+ # # Returns the output size in bytes
128+ KeccakContext .bits shr 3
129+
130+ template internalBlockSize * (H: type KeccakContext ): int =
131+ # # Returns the byte size of the hash function ingested blocks
132+ 2 * (KeccakContext .bits shr 3 )
133+
134+ func init * (ctx: var KeccakContext ) {.inline .} =
135+ # # Initialize or reinitialize a Keccak context
136+ ctx.reset ()
137+
138+ func absorb * (ctx: var KeccakContext , message: openArray [byte ]) =
139+ # # Absorb a message in the Keccak sponge state
140+ # #
141+ # # Security note: the tail of your message might be stored
142+ # # in an internal buffer.
143+ # # if sensitive content is used, ensure that
144+ # # `ctx.finish(...)` and `ctx.clear()` are called as soon as possible.
145+ # # Additionally ensure that the message(s) passed were stored
146+ # # in memory considered secure for your threat model.
147+
148+ if message.len == 0 :
149+ return
150+
151+ var pos = int ctx.absorb_offset
152+ var cur = 0
153+ var bytesLeft = message.len
154+
155+ # We follow the "absorb-permute-squeeze" approach
156+ # originally defined by the Keccak team.
157+ # It is compatible with SHA-3 hash spec.
158+ # See https://eprint.iacr.org/2022/1340.pdf
159+ #
160+ # There are no transition/permutation between squeezing -> absorbing
161+ # And within this `absorb` function
162+ # the state pos == ctx.rate()
163+ # is always followed by a permute and setting `pos = 0`
164+
165+ if (pos mod ctx.rate ()) != 0 and pos+ bytesLeft >= ctx.rate ():
166+ # Previous partial update, fill the state and do one permutation
167+ let free = ctx.rate () - pos
168+ ctx.buf.rawCopy (dStart = pos, message, sStart = 0 , len = free)
169+ ctx.absorbBuffer ()
170+ pos = 0
171+ cur = free
172+ bytesLeft -= free
173+
174+ if bytesLeft >= ctx.rate ():
175+ # Process multiple blocks
176+ let numBlocks = bytesLeft div ctx.rate ()
177+ ctx.H.hashMessageBlocks_generic (message.asUnchecked () +% cur, numBlocks)
178+ cur += numBlocks * ctx.rate ()
179+ bytesLeft -= numBlocks * ctx.rate ()
180+
181+ if bytesLeft != 0 :
182+ # Store the tail in buffer
183+ ctx.buf.rawCopy (dStart = pos, message, sStart = cur, len = bytesLeft)
184+
185+ # Epilogue
186+ ctx.absorb_offset = int32 bytesLeft
187+ # Signal that the next squeeze transition needs a permute
188+ ctx.squeeze_offset = int32 ctx.rate ()
189+
190+ func squeeze * (ctx: var KeccakContext , digest: var openArray [byte ]) =
191+ if digest.len == 0 :
192+ return
193+
194+ var pos = ctx.squeeze_offset
195+ var cur = 0
196+ var bytesLeft = digest.len
197+
198+ if pos == ctx.rate ():
199+ # Transition from absorbing to squeezing
200+ # This state can only come from `absorb` function
201+ # as within `squeeze`, pos == ctx.rate() is always followed
202+ # by a permute and pos = 0
203+ ctx.H.xorInPartial (ctx.buf.toOpenArray (0 , ctx.absorb_offset- 1 ))
204+ ctx.H.pad (ctx.absorb_offset, ctx.delimiter, ctx.rate ())
205+ ctx.H.permute_generic (NumRounds = 24 )
206+ pos = 0
207+ ctx.absorb_offset = 0
208+
209+ if (pos mod ctx.rate ()) != 0 and pos+ bytesLeft >= ctx.rate ():
210+ # Previous partial squeeze, fill up to rate and do one permutation
211+ let free = ctx.rate () - pos
212+ ctx.H.copyOutPartial (hByteOffset = pos, digest.toOpenArray (0 , free- 1 ))
213+ ctx.H.permute_generic (NumRounds = 24 )
214+ pos = 0
215+ ctx.absorb_offset = 0
216+ cur = free
217+ bytesLeft -= free
218+
219+ if bytesLeft >= ctx.rate ():
220+ # Process multiple blocks
221+ let numBlocks = bytesLeft div ctx.rate ()
222+ ctx.H.squeezeDigestBlocks_generic (digest.asUnchecked () +% cur, numBlocks)
223+ ctx.absorb_offset = 0
224+ cur += numBlocks * ctx.rate ()
225+ bytesLeft -= numBlocks * ctx.rate ()
226+
227+ if bytesLeft != 0 :
228+ # Output the tail
229+ ctx.H.copyOutPartial (hByteOffset = pos, digest.toOpenArray (cur, bytesLeft- 1 ))
230+
231+ # Epilogue
232+ ctx.squeeze_offset = int32 bytesLeft
233+ # We don't signal absorb_offset to permute the state if called next
234+ # as per https://eprint.iacr.org/2023/522.pdf
235+ # https://hackmd.io/@7dpNYqjKQGeYC7wMlPxHtQ/ByIbpfX9c#2-SAFE-definition
236+
237+ func update * (ctx: var KeccakContext , message: openArray [byte ]) =
238+ # # Append a message to a Keccak context
239+ # # for incremental Keccak computation
240+ # #
241+ # # Security note: the tail of your message might be stored
242+ # # in an internal buffer.
243+ # # if sensitive content is used, ensure that
244+ # # `ctx.finish(...)` and `ctx.clear()` are called as soon as possible.
245+ # # Additionally ensure that the message(s) passed was(were) stored
246+ # # in memory considered secure for your threat model.
247+ ctx.absorb (message)
248+
249+ func finish * [N: static int ](ctx: var KeccakContext , digest: var array [N, byte ]) =
250+ # # Finalize a Keccak computation and output the
251+ # # message digest to the `digest` buffer
252+ # #
253+ # # Security note: this does not clear the internal buffer.
254+ # # if sensitive content is used, use "ctx.clear()"
255+ # # and also make sure that the message(s) passed were stored
256+ # # in memory considered secure for your threat model.
257+ ctx.squeeze (digest)
258+
259+ func clear * (ctx: var KeccakContext ) =
260+ # # Clear the context internal buffers
261+ # TODO : ensure compiler cannot optimize the code away
262+ ctx.reset ()
263+
264+ when isMainModule :
265+ import constantine/ serialization/ codecs
266+
267+ var msg: array [32 , byte ]
268+ var digest: array [32 , byte ]
269+ var ctx: keccak256
270+
271+ ctx.init ()
272+ ctx.update (msg)
273+ ctx.finish (digest)
274+
275+ echo digest.toHex ()
0 commit comments